From 4f6e4488a55f7e3ba3f7485d78177f793c0eab9a Mon Sep 17 00:00:00 2001 From: Charles McGarvey Date: Fri, 1 Jul 2011 02:45:57 -0600 Subject: [PATCH] import stlplus 3.7 --- src/stlplus/containers/containers.hpp | 46 +- src/stlplus/containers/containers_fixes.hpp | 264 +- src/stlplus/containers/copy_functors.hpp | 132 +- src/stlplus/containers/digraph.hpp | 1014 ++-- src/stlplus/containers/digraph.tpp | 2966 ++++++------ src/stlplus/containers/exceptions.hpp | 142 +- src/stlplus/containers/foursome.hpp | 120 +- src/stlplus/containers/foursome.tpp | 131 +- src/stlplus/containers/hash.hpp | 410 +- src/stlplus/containers/hash.tpp | 1316 +++--- src/stlplus/containers/matrix.hpp | 126 +- src/stlplus/containers/matrix.tpp | 430 +- src/stlplus/containers/ntree.hpp | 763 +-- src/stlplus/containers/ntree.tpp | 1875 ++++---- src/stlplus/containers/safe_iterator.hpp | 310 +- src/stlplus/containers/safe_iterator.tpp | 730 ++- src/stlplus/containers/simple_ptr.hpp | 546 +-- src/stlplus/containers/simple_ptr.tpp | 733 +-- src/stlplus/containers/smart_ptr.hpp | 443 +- src/stlplus/containers/smart_ptr.tpp | 690 +-- src/stlplus/containers/triple.hpp | 110 +- src/stlplus/containers/triple.tpp | 123 +- src/stlplus/portability/build.cpp | 148 +- src/stlplus/portability/build.hpp | 85 +- src/stlplus/portability/debug.cpp | 374 +- src/stlplus/portability/debug.hpp | 257 +- src/stlplus/portability/dprintf.cpp | 184 +- src/stlplus/portability/dprintf.hpp | 244 +- src/stlplus/portability/dynaload.cpp | 368 +- src/stlplus/portability/dynaload.hpp | 172 +- src/stlplus/portability/file_system.cpp | 2179 ++++----- src/stlplus/portability/file_system.hpp | 403 +- src/stlplus/portability/inf.cpp | 2964 ++++++------ src/stlplus/portability/inf.hpp | 456 +- src/stlplus/portability/ip_sockets.cpp | 1926 ++++---- src/stlplus/portability/ip_sockets.hpp | 466 +- src/stlplus/portability/portability.hpp | 56 +- .../portability/portability_exceptions.hpp | 66 +- src/stlplus/portability/portability_fixes.cpp | 78 +- src/stlplus/portability/portability_fixes.hpp | 254 +- src/stlplus/portability/subprocesses.cpp | 4192 +++++++++-------- src/stlplus/portability/subprocesses.hpp | 570 +-- src/stlplus/portability/tcp.hpp | 34 +- src/stlplus/portability/tcp_sockets.cpp | 238 +- src/stlplus/portability/tcp_sockets.hpp | 770 +-- src/stlplus/portability/time.cpp | 258 +- src/stlplus/portability/time.hpp | 110 +- src/stlplus/portability/udp_sockets.cpp | 334 +- src/stlplus/portability/udp_sockets.hpp | 536 +-- src/stlplus/portability/version.cpp | 40 +- src/stlplus/portability/version.hpp | 48 +- src/stlplus/portability/wildcard.cpp | 328 +- src/stlplus/portability/wildcard.hpp | 70 +- 53 files changed, 15979 insertions(+), 15649 deletions(-) diff --git a/src/stlplus/containers/containers.hpp b/src/stlplus/containers/containers.hpp index e99d313..1363b6d 100644 --- a/src/stlplus/containers/containers.hpp +++ b/src/stlplus/containers/containers.hpp @@ -1,23 +1,23 @@ -#ifndef STLPLUS_CONTAINERS -#define STLPLUS_CONTAINERS -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Allows all the STLplus containers to be included in one go - -//////////////////////////////////////////////////////////////////////////////// - -#include "digraph.hpp" -#include "foursome.hpp" -#include "hash.hpp" -#include "matrix.hpp" -#include "ntree.hpp" -#include "smart_ptr.hpp" -#include "triple.hpp" - -//////////////////////////////////////////////////////////////////////////////// -#endif +#ifndef STLPLUS_CONTAINERS +#define STLPLUS_CONTAINERS +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Allows all the STLplus containers to be included in one go + +//////////////////////////////////////////////////////////////////////////////// + +#include "digraph.hpp" +#include "foursome.hpp" +#include "hash.hpp" +#include "matrix.hpp" +#include "ntree.hpp" +#include "smart_ptr.hpp" +#include "triple.hpp" + +//////////////////////////////////////////////////////////////////////////////// +#endif diff --git a/src/stlplus/containers/containers_fixes.hpp b/src/stlplus/containers/containers_fixes.hpp index f244323..75ceb1d 100644 --- a/src/stlplus/containers/containers_fixes.hpp +++ b/src/stlplus/containers/containers_fixes.hpp @@ -1,132 +1,132 @@ -#ifndef STLPLUS_CONTAINERS_FIXES -#define STLPLUS_CONTAINERS_FIXES -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Contains work arounds for OS or Compiler specific problems with container -// templates - -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Unnecessary compiler warnings -//////////////////////////////////////////////////////////////////////////////// - -#ifdef _MSC_VER -// Microsoft Visual Studio -// shut up the following irritating warnings -// 4786 - VC6, identifier string exceeded maximum allowable length and was truncated (only affects debugger) -// 4305 - VC6, identifier type was converted to a smaller type -// 4503 - VC6, decorated name was longer than the maximum the compiler allows (only affects debugger) -// 4309 - VC6, type conversion operation caused a constant to exceeded the space allocated for it -// 4290 - VC6, C++ exception specification ignored -// 4800 - VC6, forcing value to bool 'true' or 'false' (performance warning) -// 4355 - VC6, 'this' : used in base member initializer list -// 4675 - VC7.1, "change" in function overload resolution _might_ have altered program -// 4996 - VC8, 'xxxx' was declared deprecated -#pragma warning(disable: 4786 4305 4503 4309 4290 4800 4355 4675 4996) -#endif - -#ifdef __BORLANDC__ -// Borland -// Shut up the following irritating warnings -// 8026 - Functions with exception specifications are not expanded inline -// 8027 - Functions with xxx are not expanded inline -#pragma warn -8026 -#pragma warn -8027 -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Problems with the typename keyword -//////////////////////////////////////////////////////////////////////////////// - -// There are problems with using the 'typename' keyword. Technically, if you -// use a type member of a template class (i.e. a type declared within the -// template class by a local typedef), you need to tell the compiler that it -// is a type name. This is because the compiler cannot work out whether a -// member is a type, a method or a data field at compile time. However, -// support for the typename keyword has traditionally been incomplete in both -// gcc and Visual Studio. I have used macros to try to resolve this issue. The -// macros add the keyword for compiler versions that require it and omit it -// for compiler versions that do not support it - -// There are five places where typename keywords cause problems: -// -// 1) in a typedef where a template class's member type is being mapped onto -// a type definition within another template class or function -// e.g. template fn () { -// typedef typename someclass::member_type local_type; -// ^^^^^^^^ -// 2) in a function parameter declaration, with similar rules to the above -// e.g. template fn (typename someclass::member_type) -// ^^^^^^^^ -// 3) in instantiating a template, the parameter to the template, with similar rules to the above -// e.g. template_class::member_type> -// ^^^^^^^^ -// 4) Return expressions -// e.g. return typename ntree::const_iterator(this,m_root); -// ^^^^^^^^ -// 5) Creating temporary objects when passing arguments to a function or constructor -// e.g. return typename ntree::const_prefix_iterator(typename ntree::const_iterator(this,m_root)); -// ^^^^^^^^ -// Note that the typename keyword is only required when the type being referred to is a member of a template class -// -// So far it *seems* as if all compilers either require all of them or none of -// them, so this set of situations can be handled by a single macro - -// default values, overridden for individual problem cases below -#define TYPENAME typename - -// GCC -// - pre-version 3 didn't handle typename in any of these cases -// - version 3 onwards, typename is required for all three cases as per default -#ifdef __GNUC__ -#if __GNUC__ < 3 -#undef TYPENAME -#define TYPENAME -#endif -#endif - -// Visual Studio -// - version 6 (compiler v.12) cannot handle typename in any of these cases -// - version 7 (.NET) (compiler v.13) requires a typename in a parameter specification but supports all -// - version 8 (2005) (compiler v.14) requires parameters and templates, supports all -#ifdef _MSC_VER -#if _MSC_VER <= 1200 -#undef TYPENAME -#define TYPENAME -#endif -#endif - -// Borland -// - doesn't handle typename in 5.5, does in 5.82, not sure about other cases -#ifdef __BORLANDC__ -#if __BORLANDC__ <= 0x550 -#undef TYPENAME -#define TYPENAME -#endif -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Member templates -// e.g. a template function in a template class - -// Not all compilers support them - this fix can be used to disable member -// templates for compilers that don't. Unfortunately that means that some -// functionality will be missing for those compilers. - -#define STLPLUS_MEMBER_TEMPLATES - -// Visual Studio v6 (compiler version 12) does not support them -#ifdef _MSC_VER -#if _MSC_VER <= 1200 -#undef STLPLUS_MEMBER_TEMPLATES -#endif -#endif - -//////////////////////////////////////////////////////////////////////////////// -#endif +#ifndef STLPLUS_CONTAINERS_FIXES +#define STLPLUS_CONTAINERS_FIXES +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Contains work arounds for OS or Compiler specific problems with container +// templates + +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Unnecessary compiler warnings +//////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +// Microsoft Visual Studio +// shut up the following irritating warnings +// 4786 - VC6, identifier string exceeded maximum allowable length and was truncated (only affects debugger) +// 4305 - VC6, identifier type was converted to a smaller type +// 4503 - VC6, decorated name was longer than the maximum the compiler allows (only affects debugger) +// 4309 - VC6, type conversion operation caused a constant to exceeded the space allocated for it +// 4290 - VC6, C++ exception specification ignored +// 4800 - VC6, forcing value to bool 'true' or 'false' (performance warning) +// 4355 - VC6, 'this' : used in base member initializer list +// 4675 - VC7.1, "change" in function overload resolution _might_ have altered program +// 4996 - VC8, 'xxxx' was declared deprecated +#pragma warning(disable: 4786 4305 4503 4309 4290 4800 4355 4675 4996) +#endif + +#ifdef __BORLANDC__ +// Borland +// Shut up the following irritating warnings +// 8026 - Functions with exception specifications are not expanded inline +// 8027 - Functions with xxx are not expanded inline +#pragma warn -8026 +#pragma warn -8027 +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Problems with the typename keyword +//////////////////////////////////////////////////////////////////////////////// + +// There are problems with using the 'typename' keyword. Technically, if you +// use a type member of a template class (i.e. a type declared within the +// template class by a local typedef), you need to tell the compiler that it +// is a type name. This is because the compiler cannot work out whether a +// member is a type, a method or a data field at compile time. However, +// support for the typename keyword has traditionally been incomplete in both +// gcc and Visual Studio. I have used macros to try to resolve this issue. The +// macros add the keyword for compiler versions that require it and omit it +// for compiler versions that do not support it + +// There are five places where typename keywords cause problems: +// +// 1) in a typedef where a template class's member type is being mapped onto +// a type definition within another template class or function +// e.g. template fn () { +// typedef typename someclass::member_type local_type; +// ^^^^^^^^ +// 2) in a function parameter declaration, with similar rules to the above +// e.g. template fn (typename someclass::member_type) +// ^^^^^^^^ +// 3) in instantiating a template, the parameter to the template, with similar rules to the above +// e.g. template_class::member_type> +// ^^^^^^^^ +// 4) Return expressions +// e.g. return typename ntree::const_iterator(this,m_root); +// ^^^^^^^^ +// 5) Creating temporary objects when passing arguments to a function or constructor +// e.g. return typename ntree::const_prefix_iterator(typename ntree::const_iterator(this,m_root)); +// ^^^^^^^^ +// Note that the typename keyword is only required when the type being referred to is a member of a template class +// +// So far it *seems* as if all compilers either require all of them or none of +// them, so this set of situations can be handled by a single macro + +// default values, overridden for individual problem cases below +#define TYPENAME typename + +// GCC +// - pre-version 3 didn't handle typename in any of these cases +// - version 3 onwards, typename is required for all three cases as per default +#ifdef __GNUC__ +#if __GNUC__ < 3 +#undef TYPENAME +#define TYPENAME +#endif +#endif + +// Visual Studio +// - version 6 (compiler v.12) cannot handle typename in any of these cases +// - version 7 (.NET) (compiler v.13) requires a typename in a parameter specification but supports all +// - version 8 (2005) (compiler v.14) requires parameters and templates, supports all +#ifdef _MSC_VER +#if _MSC_VER <= 1200 +#undef TYPENAME +#define TYPENAME +#endif +#endif + +// Borland +// - doesn't handle typename in 5.5, does in 5.82, not sure about other cases +#ifdef __BORLANDC__ +#if __BORLANDC__ <= 0x550 +#undef TYPENAME +#define TYPENAME +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Member templates +// e.g. a template function in a template class + +// Not all compilers support them - this fix can be used to disable member +// templates for compilers that don't. Unfortunately that means that some +// functionality will be missing for those compilers. + +#define STLPLUS_MEMBER_TEMPLATES + +// Visual Studio v6 (compiler version 12) does not support them +#ifdef _MSC_VER +#if _MSC_VER <= 1200 +#undef STLPLUS_MEMBER_TEMPLATES +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +#endif diff --git a/src/stlplus/containers/copy_functors.hpp b/src/stlplus/containers/copy_functors.hpp index 68f2b21..ce530b8 100644 --- a/src/stlplus/containers/copy_functors.hpp +++ b/src/stlplus/containers/copy_functors.hpp @@ -1,66 +1,66 @@ -#ifndef STLPLUS_COPY_FUNCTORS -#define STLPLUS_COPY_FUNCTORS -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// The function constructor classes below are used by the smart_ptr and the -// simple_ptr classes. They provide three (well ok, two) copying mechanisms. -// These classes have been separated from the smart_ptr header by DJDM, as -// the simple_ptr classes now also use them. - -//////////////////////////////////////////////////////////////////////////////// -#include "containers_fixes.hpp" -#include "exceptions.hpp" - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // copy functors implementing the three possible copy semantics - - // constructor_copy uses the copy constructor of the object - used for simple types - - template - class constructor_copy - { - public: - T* operator() (const T& from) throw() - { - return new T(from); - } - }; - - // clone_copy uses the clone method of the object - used for polymorphic types - - template - class clone_copy - { - public: - T* operator() (const T& from) throw() - { - return from.clone(); - } - }; - - // no_copy throws an exception - used for types that cannot be copied - - template - class no_copy - { - public: - T* operator() (const T& from) throw(illegal_copy) - { - throw illegal_copy("no_copy functor called"); - return 0; - } - }; - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#endif +#ifndef STLPLUS_COPY_FUNCTORS +#define STLPLUS_COPY_FUNCTORS +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// The function constructor classes below are used by the smart_ptr and the +// simple_ptr classes. They provide three (well ok, two) copying mechanisms. +// These classes have been separated from the smart_ptr header by DJDM, as +// the simple_ptr classes now also use them. + +//////////////////////////////////////////////////////////////////////////////// +#include "containers_fixes.hpp" +#include "exceptions.hpp" + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // copy functors implementing the three possible copy semantics + + // constructor_copy uses the copy constructor of the object - used for simple types + + template + class constructor_copy + { + public: + T* operator() (const T& from) throw() + { + return new T(from); + } + }; + + // clone_copy uses the clone method of the object - used for polymorphic types + + template + class clone_copy + { + public: + T* operator() (const T& from) throw() + { + return from.clone(); + } + }; + + // no_copy throws an exception - used for types that cannot be copied + + template + class no_copy + { + public: + T* operator() (const T& from) throw(illegal_copy) + { + throw illegal_copy("no_copy functor called"); + return 0; + } + }; + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#endif diff --git a/src/stlplus/containers/digraph.hpp b/src/stlplus/containers/digraph.hpp index 7b603c1..e4ffb5f 100644 --- a/src/stlplus/containers/digraph.hpp +++ b/src/stlplus/containers/digraph.hpp @@ -1,507 +1,507 @@ -#ifndef STLPLUS_DIGRAPH -#define STLPLUS_DIGRAPH -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// STL-style Directed graph template component -// Digraph stands for directed-graph, i.e. all arcs have a direction - -//////////////////////////////////////////////////////////////////////////////// -#include "containers_fixes.hpp" -#include "safe_iterator.hpp" -#include "exceptions.hpp" -#include -#include -#include - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // Internals - - template class digraph_node; - template class digraph_arc; - template class digraph; - - //////////////////////////////////////////////////////////////////////////////// - // The Digraph iterator classes - // a digraph_iterator points to a node whilst a digraph_arc_iterator points to an arc - // Note that these are redefined as: - // digraph::iterator - points to a non-const node - // digraph::const_iterator - points to a const node - // digraph::arc_iterator - points to a non-const arc - // digraph::const_arc_iterator - points to a const arc - // and this is the form in which they should be used - - template - class digraph_iterator : public safe_iterator, digraph_node > - { - public: - friend class digraph; - - // local type definitions - // an iterator points to an object whilst a const_iterator points to a const object - typedef digraph_iterator iterator; - typedef digraph_iterator const_iterator; - typedef digraph_iterator this_iterator; - typedef NRef reference; - typedef NPtr pointer; - - // constructor to create a null iterator - you must assign a valid value to this iterator before using it - digraph_iterator(void); - ~digraph_iterator(void); - - // Type conversion methods allow const_iterator and iterator to be converted - // convert an iterator/const_iterator to a const_iterator - const_iterator constify(void) const; - // convert an iterator/const_iterator to an iterator - iterator deconstify(void) const; - - // increment/decrement operators used to step through the set of all nodes in a graph - // it is only legal to increment a valid iterator - // pre-increment - this_iterator& operator ++ (void) - throw(null_dereference,end_dereference); - // post-increment - this_iterator operator ++ (int) - throw(null_dereference,end_dereference); - // pre-decrement - this_iterator& operator -- (void) - throw(null_dereference,end_dereference); - // post-decrement - this_iterator operator -- (int) - throw(null_dereference,end_dereference); - - // test useful for testing whether iteration has completed and for inclusion in other containers - // Note: this class also inherits the safe_iterator methods: valid(), null(), end() - bool operator == (const this_iterator& r) const; - bool operator != (const this_iterator& r) const; - bool operator < (const this_iterator& r) const; - - // access the node data - a const_iterator gives you a const element, an iterator a non-const element - // it is illegal to dereference an invalid (i.e. null or end) iterator - reference operator*(void) const - throw(null_dereference,end_dereference); - pointer operator->(void) const - throw(null_dereference,end_dereference); - - public: - // constructor used by digraph to create a non-null iterator - explicit digraph_iterator(digraph_node* node); - // constructor used by digraph to create an end iterator - explicit digraph_iterator(const digraph* owner); - // used to create an alias of an iterator - explicit digraph_iterator(const safe_iterator, digraph_node >& iterator); - }; - - //////////////////////////////////////////////////////////////////////////////// - - template - class digraph_arc_iterator : public safe_iterator, digraph_arc > - { - public: - friend class digraph; - - // local type definitions - // an iterator points to an object whilst a const_iterator points to a const object - typedef digraph_arc_iterator iterator; - typedef digraph_arc_iterator const_iterator; - typedef digraph_arc_iterator this_iterator; - typedef ARef reference; - typedef APtr pointer; - - // constructor to create a null iterator - you must assign a valid value to this iterator before using it - digraph_arc_iterator(void); - ~digraph_arc_iterator(void); - - // Type conversion methods allow const_iterator and iterator to be converted - // convert an iterator/const_iterator to a const_iterator - const_iterator constify(void) const; - // convert an iterator/const_iterator to an iterator - iterator deconstify(void) const; - - // increment/decrement operators used to step through the set of all nodes in a graph - // it is only legal to increment a valid iterator - // pre-increment - this_iterator& operator ++ (void) - throw(null_dereference,end_dereference); - // post-increment - this_iterator operator ++ (int) - throw(null_dereference,end_dereference); - // pre-decrement - this_iterator& operator -- (void) - throw(null_dereference,end_dereference); - // post-decrement - this_iterator operator -- (int) - throw(null_dereference,end_dereference); - - // test useful for testing whether iteration has completed and for inclusion in other containers - // Note: this class also inherits the safe_iterator methods: valid(), null(), end() - bool operator == (const this_iterator&) const; - bool operator != (const this_iterator&) const; - bool operator < (const this_iterator&) const; - - // access the node data - a const_iterator gives you a const element, an iterator a non-const element - // it is illegal to dereference an invalid (i.e. null or end) iterator - reference operator*(void) const - throw(null_dereference,end_dereference); - pointer operator->(void) const - throw(null_dereference,end_dereference); - - public: - // constructor used by digraph to create a non-null iterator - explicit digraph_arc_iterator(digraph_arc* arc); - // constructor used by digraph to create an end iterator - explicit digraph_arc_iterator(const digraph* owner); - // used to create an alias of an iterator - explicit digraph_arc_iterator(const safe_iterator, digraph_arc >& iterator); - }; - - //////////////////////////////////////////////////////////////////////////////// - // The Graph class - // NT is the Node type and AT is the Arc type - //////////////////////////////////////////////////////////////////////////////// - - template - class digraph - { - public: - // STL-like typedefs for the types and iterators - typedef NT node_type; - typedef AT arc_type; - typedef digraph_iterator iterator; - typedef digraph_iterator const_iterator; - typedef digraph_arc_iterator arc_iterator; - typedef digraph_arc_iterator const_arc_iterator; - - // supplementary types used throughout - - // a path is represented as a vector of arcs so the forward traversal is - // done by going from begin() to end() or 0 to size-1 - of course a backward - // traversal can be done by traversing the vector backwards - typedef std::vector arc_vector; - typedef std::vector const_arc_vector; - const_arc_vector constify_arcs(const arc_vector&) const - throw(wrong_object,null_dereference,end_dereference); - arc_vector deconstify_arcs(const const_arc_vector&) const - throw(wrong_object,null_dereference,end_dereference); - - // a path vector is a vector of paths used to represent all the paths from one node to another - // there is no particular ordering to the paths in the vector - typedef std::vector path_vector; - typedef std::vector const_path_vector; - const_path_vector constify_paths(const path_vector&) const - throw(wrong_object,null_dereference,end_dereference); - path_vector deconstify_paths(const const_path_vector&) const - throw(wrong_object,null_dereference,end_dereference); - - // a node vector is a simple vector of nodes used to represent the reachable sets - // there is no particular ordering to the nodes in the vector - typedef std::vector node_vector; - typedef std::vector const_node_vector; - const_node_vector constify_nodes(const node_vector&) const - throw(wrong_object,null_dereference,end_dereference); - node_vector deconstify_nodes(const const_node_vector&) const - throw(wrong_object,null_dereference,end_dereference); - - // callback used in the path algorithms to select which arcs to consider - typedef bool (*arc_select_fn) (const digraph&, const_arc_iterator); - - // a value representing an unknown offset - // Note that it's static so use in the form digraph::npos() - static unsigned npos(void); - - ////////////////////////////////////////////////////////////////////////// - // Constructors, destructors and copies - - digraph(void); - ~digraph(void); - - // copy constructor and assignment both copy the graph - digraph(const digraph&); - digraph& operator=(const digraph&); - - ////////////////////////////////////////////////////////////////////////// - // Basic Node functions - // Nodes are referred to by iterators created when the node is inserted. - // Iterators remain valid unless the node is erased (they are list iterators, so no resize problems) - // It is also possible to walk through all the nodes using a list-like start() to end() loop - // Each node has a set of input arcs and output arcs. These are indexed by an unsigned i.e. they form a vector. - // The total number of inputs is the fanin and the total number of outputs is the fanout. - // The contents of the node (type NT) are accessed, of course, by dereferencing the node iterator. - - // tests for the number of nodes and the special test for zero nodes - bool empty(void) const; - unsigned size(void) const; - - // add a new node and return its iterator - iterator insert(const NT& node_data); - - // remove a node and return the iterator to the next node - // erasing a node erases its arcs - iterator erase(iterator) - throw(wrong_object,null_dereference,end_dereference); - // remove all nodes - void clear(void); - - // traverse all the nodes in no particular order using STL-style iteration - const_iterator begin(void) const; - iterator begin(void); - const_iterator end(void) const; - iterator end(void); - - // access the inputs of this node - // the fanin is the number of inputs and the inputs are accessed using an index from 0..fanin-1 - unsigned fanin(const_iterator) const - throw(wrong_object,null_dereference,end_dereference); - unsigned fanin(iterator) - throw(wrong_object,null_dereference,end_dereference); - const_arc_iterator input(const_iterator, unsigned) const - throw(wrong_object,null_dereference,end_dereference,std::out_of_range); - arc_iterator input(iterator, unsigned) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range); - - // access the outputs of this node - // the fanout is the number of outputs and the outputs are accessed using an index from 0..fanout-1 - unsigned fanout(const_iterator) const - throw(wrong_object,null_dereference,end_dereference); - unsigned fanout(iterator) - throw(wrong_object,null_dereference,end_dereference); - const_arc_iterator output(const_iterator, unsigned) const - throw(wrong_object,null_dereference,end_dereference,std::out_of_range); - arc_iterator output(iterator, unsigned) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range); - - // convenience routines for getting the set of all inputs or all outputs as vectors - const_arc_vector inputs(const_iterator) const - throw(wrong_object,null_dereference,end_dereference); - arc_vector inputs(iterator) - throw(wrong_object,null_dereference,end_dereference); - const_arc_vector outputs(const_iterator) const - throw(wrong_object,null_dereference,end_dereference); - arc_vector outputs(iterator) - throw(wrong_object,null_dereference,end_dereference); - - // find the output index of an arc which goes from this node - // returns digraph::npos if the arc is not an output of from - unsigned output_offset(const_iterator from, const_arc_iterator arc) const - throw(wrong_object,null_dereference,end_dereference); - unsigned output_offset(iterator from, arc_iterator arc) - throw(wrong_object,null_dereference,end_dereference); - // ditto for an input arc - unsigned input_offset(const_iterator to, const_arc_iterator arc) const - throw(wrong_object,null_dereference,end_dereference); - unsigned input_offset(iterator to, arc_iterator arc) - throw(wrong_object,null_dereference,end_dereference); - - ////////////////////////////////////////////////////////////////////////// - // Basic Arc functions - // to avoid name conflicts, arc functions have the arc_ prefix - // Arcs, like nodes, are referred to by a list iterator which is returned by the arc_insert function - // They may also be visited from arc_begin() to arc_end() - // Each arc has a from field and a to field which contain the node iterators of the endpoints of the arc - // Of course, the arc data can be accessed by simply dereferencing the iterator - - // tests for the number of arcs and the special test for zero arcs - bool arc_empty (void) const; - unsigned arc_size(void) const; - - // add a new arc and return its iterator - arc_iterator arc_insert(iterator from, iterator to, const AT& arc_data = AT()) - throw(wrong_object,null_dereference,end_dereference); - - // remove an arc and return the iterator to the next arc - arc_iterator arc_erase(arc_iterator) - throw(wrong_object,null_dereference,end_dereference); - // remove all arcs - void arc_clear(void); - - // traverse all the arcs in no particular order using STL-style iteration - const_arc_iterator arc_begin(void) const; - arc_iterator arc_begin(void); - const_arc_iterator arc_end(void) const; - arc_iterator arc_end(void); - - // find the node that an arc points from or to - const_iterator arc_from(const_arc_iterator) const - throw(wrong_object,null_dereference,end_dereference); - iterator arc_from(arc_iterator) - throw(wrong_object,null_dereference,end_dereference); - const_iterator arc_to(const_arc_iterator) const - throw(wrong_object,null_dereference,end_dereference); - iterator arc_to(arc_iterator) - throw(wrong_object,null_dereference,end_dereference); - - // reconnect an arc to a different from and to node - void arc_move(arc_iterator arc, iterator from, iterator to) - throw(wrong_object,null_dereference,end_dereference); - // reconnect just the from node - void arc_move_from(arc_iterator arc, iterator from) - throw(wrong_object,null_dereference,end_dereference); - // reconnect just the to node - void arc_move_to(arc_iterator arc, iterator to) - throw(wrong_object,null_dereference,end_dereference); - // reverse the arc direction so that to becomes from and vice-versa - void arc_flip(arc_iterator arc) - throw(wrong_object,null_dereference,end_dereference); - - //////////////////////////////////////////////////////////////////////////////// - // Adjacency algorithms - - // test whether the nodes are adjacent i.e. whether there is an arc going from from to to - bool adjacent(const_iterator from, const_iterator to) const - throw(wrong_object,null_dereference,end_dereference); - bool adjacent(iterator from, iterator to) - throw(wrong_object,null_dereference,end_dereference); - - // as above, but returns the arc that makes the nodes adjacent - // returns the first arc if there's more than one, returns arc_end() if there are none - const_arc_iterator adjacent_arc(const_iterator from, const_iterator to) const - throw(wrong_object,null_dereference,end_dereference); - arc_iterator adjacent_arc(iterator from, iterator to) - throw(wrong_object,null_dereference,end_dereference); - - // as above, but returns the set of all arcs that make two nodes adjacent (there may be more than one) - // returns an empty vector if there are none - const_arc_vector adjacent_arcs(const_iterator from, const_iterator to) const - throw(wrong_object,null_dereference,end_dereference); - arc_vector adjacent_arcs(iterator from, iterator to) - throw(wrong_object,null_dereference,end_dereference); - - // return the adjacency sets for the node inputs or outputs, i.e. the set of nodes adjacent to this node - // each adjacent node will only be entered once even if there are multiple arcs between the nodes - const_node_vector input_adjacencies(const_iterator to) const - throw(wrong_object,null_dereference,end_dereference); - node_vector input_adjacencies(iterator to) - throw(wrong_object,null_dereference,end_dereference); - const_node_vector output_adjacencies(const_iterator from) const - throw(wrong_object,null_dereference,end_dereference); - node_vector output_adjacencies(iterator from) - throw(wrong_object,null_dereference,end_dereference); - - //////////////////////////////////////////////////////////////////////////////// - // Topographical Sort Algorithm - // This generates a node ordering such that each node is visited after its fanin nodes. - - // This only generates a valid ordering for a DAG. - - // The return value is a pair : - // - the node vector which is a set of iterators to the nodes in sorted order - // - the arc vector is the set of backward ards that were broken to achieve the sort - // If the arc vector is empty then the graph formed a DAG. - - // The arc selection callback can be used to ignore arcs that are not part - // of the ordering, i.e. arcs that are meant to be backwards arcs - - std::pair sort(arc_select_fn = 0) const; - std::pair sort(arc_select_fn = 0); - - // Simplified variant of above for graphs that are known to be DAGs. - // If the sort fails due to backward arcs, the - // return vector is empty. Note that this will also be empty if the graph - // has no nodes in it, so use the empty() method to differentiate. - - const_node_vector dag_sort(arc_select_fn = 0) const; - node_vector dag_sort(arc_select_fn = 0); - - //////////////////////////////////////////////////////////////////////////////// - // Basic Path Algorithms - // A path is a series of arcs - you can use arc_from and arc_to to convert - // that into a series of nodes. All the path algorithms take an arc_select - // which allows arcs to be selected or rejected for consideration in a path. - - // A selection callback function is applied to each arc in the traversal and - // returns true if the arc is to be selected and false if the arc is to be - // rejected. If no function is provided the arc is selected. If you want to - // use arc selection you should create a function with the type profile given - // by the arc_select_fn type. The select function is passed both the graph and - // the arc iterator so that it is possible to select an arc on the basis of - // the nodes it is connected to. - - // Note: I used a callback because the STL-like predicate idea wasn't working for me... - - // test for the existence of a path from from to to - bool path_exists(const_iterator from, const_iterator to, arc_select_fn = 0) const - throw(wrong_object,null_dereference,end_dereference); - bool path_exists(iterator from, iterator to, arc_select_fn = 0) - throw(wrong_object,null_dereference,end_dereference); - - // get the set of all paths from from to to - const_path_vector all_paths(const_iterator from, const_iterator to, arc_select_fn = 0) const - throw(wrong_object,null_dereference,end_dereference); - path_vector all_paths(iterator from, iterator to, arc_select_fn = 0) - throw(wrong_object,null_dereference,end_dereference); - - // get the set of all nodes that can be reached by any path from from - const_node_vector reachable_nodes(const_iterator from, arc_select_fn = 0) const - throw(wrong_object,null_dereference,end_dereference); - node_vector reachable_nodes(iterator from, arc_select_fn = 0) - throw(wrong_object,null_dereference,end_dereference); - - // get the set of all nodes that can reach to to by any path - const_node_vector reaching_nodes(const_iterator to, arc_select_fn = 0) const - throw(wrong_object,null_dereference,end_dereference); - node_vector reaching_nodes(iterator to, arc_select_fn = 0) - throw(wrong_object,null_dereference,end_dereference); - - //////////////////////////////////////////////////////////////////////////////// - // Unweighted Shortest path algorithms - - // find the shortest path from from to to - // This is an unweighted shortest path algorithm, i.e. the weight of each - // arc is assumed to be 1, so just counts the number of arcs - // if there is more than one shortest path it returns the first one - // If there are no paths, returns an empty path - const_arc_vector shortest_path(const_iterator from, const_iterator to, arc_select_fn = 0) const - throw(wrong_object,null_dereference,end_dereference); - arc_vector shortest_path(iterator from, iterator to, arc_select_fn = 0) - throw(wrong_object,null_dereference,end_dereference); - - // find the set of shortest paths from from to any other node in the graph - // that is reachable (i.e. for which path_exists() is true) - // This is an unweighted shortest path, so just counts the number of arcs - // if there is more than one shortest path to a node it returns the first one - // If there are no paths, returns an empty list - const_path_vector shortest_paths(const_iterator from, arc_select_fn = 0) const - throw(wrong_object,null_dereference,end_dereference); - path_vector shortest_paths(iterator from, arc_select_fn = 0) - throw(wrong_object,null_dereference,end_dereference); - - private: - friend class digraph_iterator; - friend class digraph_iterator; - friend class digraph_arc_iterator; - friend class digraph_arc_iterator; - - typedef std::set const_iterator_set; - typedef TYPENAME const_iterator_set::iterator const_iterator_set_iterator; - - bool path_exists_r(const_iterator from, const_iterator to, const_iterator_set& visited, arc_select_fn) const - throw(wrong_object,null_dereference,end_dereference); - - void all_paths_r(const_iterator from, const_iterator to, const_arc_vector& so_far, const_path_vector& result, arc_select_fn) const - throw(wrong_object,null_dereference,end_dereference); - - void reachable_nodes_r(const_iterator from, const_iterator_set& visited, arc_select_fn) const - throw(wrong_object,null_dereference,end_dereference); - - void reaching_nodes_r(const_iterator to, const_iterator_set& visited, arc_select_fn) const - throw(wrong_object,null_dereference,end_dereference); - - digraph_node* m_nodes_begin; - digraph_node* m_nodes_end; - digraph_arc* m_arcs_begin; - digraph_arc* m_arcs_end; - }; - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#include "digraph.tpp" -#endif +#ifndef STLPLUS_DIGRAPH +#define STLPLUS_DIGRAPH +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// STL-style Directed graph template component +// Digraph stands for directed-graph, i.e. all arcs have a direction + +//////////////////////////////////////////////////////////////////////////////// +#include "containers_fixes.hpp" +#include "safe_iterator.hpp" +#include "exceptions.hpp" +#include +#include +#include + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // Internals + + template class digraph_node; + template class digraph_arc; + template class digraph; + + //////////////////////////////////////////////////////////////////////////////// + // The Digraph iterator classes + // a digraph_iterator points to a node whilst a digraph_arc_iterator points to an arc + // Note that these are redefined as: + // digraph::iterator - points to a non-const node + // digraph::const_iterator - points to a const node + // digraph::arc_iterator - points to a non-const arc + // digraph::const_arc_iterator - points to a const arc + // and this is the form in which they should be used + + template + class digraph_iterator : public safe_iterator, digraph_node > + { + public: + friend class digraph; + + // local type definitions + // an iterator points to an object whilst a const_iterator points to a const object + typedef digraph_iterator iterator; + typedef digraph_iterator const_iterator; + typedef digraph_iterator this_iterator; + typedef NRef reference; + typedef NPtr pointer; + + // constructor to create a null iterator - you must assign a valid value to this iterator before using it + digraph_iterator(void); + ~digraph_iterator(void); + + // Type conversion methods allow const_iterator and iterator to be converted + // convert an iterator/const_iterator to a const_iterator + const_iterator constify(void) const; + // convert an iterator/const_iterator to an iterator + iterator deconstify(void) const; + + // increment/decrement operators used to step through the set of all nodes in a graph + // it is only legal to increment a valid iterator + // pre-increment + this_iterator& operator ++ (void) + throw(null_dereference,end_dereference); + // post-increment + this_iterator operator ++ (int) + throw(null_dereference,end_dereference); + // pre-decrement + this_iterator& operator -- (void) + throw(null_dereference,end_dereference); + // post-decrement + this_iterator operator -- (int) + throw(null_dereference,end_dereference); + + // test useful for testing whether iteration has completed and for inclusion in other containers + // Note: this class also inherits the safe_iterator methods: valid(), null(), end() + bool operator == (const this_iterator& r) const; + bool operator != (const this_iterator& r) const; + bool operator < (const this_iterator& r) const; + + // access the node data - a const_iterator gives you a const element, an iterator a non-const element + // it is illegal to dereference an invalid (i.e. null or end) iterator + reference operator*(void) const + throw(null_dereference,end_dereference); + pointer operator->(void) const + throw(null_dereference,end_dereference); + + public: + // constructor used by digraph to create a non-null iterator + explicit digraph_iterator(digraph_node* node); + // constructor used by digraph to create an end iterator + explicit digraph_iterator(const digraph* owner); + // used to create an alias of an iterator + explicit digraph_iterator(const safe_iterator, digraph_node >& iterator); + }; + + //////////////////////////////////////////////////////////////////////////////// + + template + class digraph_arc_iterator : public safe_iterator, digraph_arc > + { + public: + friend class digraph; + + // local type definitions + // an iterator points to an object whilst a const_iterator points to a const object + typedef digraph_arc_iterator iterator; + typedef digraph_arc_iterator const_iterator; + typedef digraph_arc_iterator this_iterator; + typedef ARef reference; + typedef APtr pointer; + + // constructor to create a null iterator - you must assign a valid value to this iterator before using it + digraph_arc_iterator(void); + ~digraph_arc_iterator(void); + + // Type conversion methods allow const_iterator and iterator to be converted + // convert an iterator/const_iterator to a const_iterator + const_iterator constify(void) const; + // convert an iterator/const_iterator to an iterator + iterator deconstify(void) const; + + // increment/decrement operators used to step through the set of all nodes in a graph + // it is only legal to increment a valid iterator + // pre-increment + this_iterator& operator ++ (void) + throw(null_dereference,end_dereference); + // post-increment + this_iterator operator ++ (int) + throw(null_dereference,end_dereference); + // pre-decrement + this_iterator& operator -- (void) + throw(null_dereference,end_dereference); + // post-decrement + this_iterator operator -- (int) + throw(null_dereference,end_dereference); + + // test useful for testing whether iteration has completed and for inclusion in other containers + // Note: this class also inherits the safe_iterator methods: valid(), null(), end() + bool operator == (const this_iterator&) const; + bool operator != (const this_iterator&) const; + bool operator < (const this_iterator&) const; + + // access the node data - a const_iterator gives you a const element, an iterator a non-const element + // it is illegal to dereference an invalid (i.e. null or end) iterator + reference operator*(void) const + throw(null_dereference,end_dereference); + pointer operator->(void) const + throw(null_dereference,end_dereference); + + public: + // constructor used by digraph to create a non-null iterator + explicit digraph_arc_iterator(digraph_arc* arc); + // constructor used by digraph to create an end iterator + explicit digraph_arc_iterator(const digraph* owner); + // used to create an alias of an iterator + explicit digraph_arc_iterator(const safe_iterator, digraph_arc >& iterator); + }; + + //////////////////////////////////////////////////////////////////////////////// + // The Graph class + // NT is the Node type and AT is the Arc type + //////////////////////////////////////////////////////////////////////////////// + + template + class digraph + { + public: + // STL-like typedefs for the types and iterators + typedef NT node_type; + typedef AT arc_type; + typedef digraph_iterator iterator; + typedef digraph_iterator const_iterator; + typedef digraph_arc_iterator arc_iterator; + typedef digraph_arc_iterator const_arc_iterator; + + // supplementary types used throughout + + // a path is represented as a vector of arcs so the forward traversal is + // done by going from begin() to end() or 0 to size-1 - of course a backward + // traversal can be done by traversing the vector backwards + typedef std::vector arc_vector; + typedef std::vector const_arc_vector; + const_arc_vector constify_arcs(const arc_vector&) const + throw(wrong_object,null_dereference,end_dereference); + arc_vector deconstify_arcs(const const_arc_vector&) const + throw(wrong_object,null_dereference,end_dereference); + + // a path vector is a vector of paths used to represent all the paths from one node to another + // there is no particular ordering to the paths in the vector + typedef std::vector path_vector; + typedef std::vector const_path_vector; + const_path_vector constify_paths(const path_vector&) const + throw(wrong_object,null_dereference,end_dereference); + path_vector deconstify_paths(const const_path_vector&) const + throw(wrong_object,null_dereference,end_dereference); + + // a node vector is a simple vector of nodes used to represent the reachable sets + // there is no particular ordering to the nodes in the vector + typedef std::vector node_vector; + typedef std::vector const_node_vector; + const_node_vector constify_nodes(const node_vector&) const + throw(wrong_object,null_dereference,end_dereference); + node_vector deconstify_nodes(const const_node_vector&) const + throw(wrong_object,null_dereference,end_dereference); + + // callback used in the path algorithms to select which arcs to consider + typedef bool (*arc_select_fn) (const digraph&, const_arc_iterator); + + // a value representing an unknown offset + // Note that it's static so use in the form digraph::npos() + static unsigned npos(void); + + ////////////////////////////////////////////////////////////////////////// + // Constructors, destructors and copies + + digraph(void); + ~digraph(void); + + // copy constructor and assignment both copy the graph + digraph(const digraph&); + digraph& operator=(const digraph&); + + ////////////////////////////////////////////////////////////////////////// + // Basic Node functions + // Nodes are referred to by iterators created when the node is inserted. + // Iterators remain valid unless the node is erased (they are list iterators, so no resize problems) + // It is also possible to walk through all the nodes using a list-like start() to end() loop + // Each node has a set of input arcs and output arcs. These are indexed by an unsigned i.e. they form a vector. + // The total number of inputs is the fanin and the total number of outputs is the fanout. + // The contents of the node (type NT) are accessed, of course, by dereferencing the node iterator. + + // tests for the number of nodes and the special test for zero nodes + bool empty(void) const; + unsigned size(void) const; + + // add a new node and return its iterator + iterator insert(const NT& node_data); + + // remove a node and return the iterator to the next node + // erasing a node erases its arcs + iterator erase(iterator) + throw(wrong_object,null_dereference,end_dereference); + // remove all nodes + void clear(void); + + // traverse all the nodes in no particular order using STL-style iteration + const_iterator begin(void) const; + iterator begin(void); + const_iterator end(void) const; + iterator end(void); + + // access the inputs of this node + // the fanin is the number of inputs and the inputs are accessed using an index from 0..fanin-1 + unsigned fanin(const_iterator) const + throw(wrong_object,null_dereference,end_dereference); + unsigned fanin(iterator) + throw(wrong_object,null_dereference,end_dereference); + const_arc_iterator input(const_iterator, unsigned) const + throw(wrong_object,null_dereference,end_dereference,std::out_of_range); + arc_iterator input(iterator, unsigned) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range); + + // access the outputs of this node + // the fanout is the number of outputs and the outputs are accessed using an index from 0..fanout-1 + unsigned fanout(const_iterator) const + throw(wrong_object,null_dereference,end_dereference); + unsigned fanout(iterator) + throw(wrong_object,null_dereference,end_dereference); + const_arc_iterator output(const_iterator, unsigned) const + throw(wrong_object,null_dereference,end_dereference,std::out_of_range); + arc_iterator output(iterator, unsigned) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range); + + // convenience routines for getting the set of all inputs or all outputs as vectors + const_arc_vector inputs(const_iterator) const + throw(wrong_object,null_dereference,end_dereference); + arc_vector inputs(iterator) + throw(wrong_object,null_dereference,end_dereference); + const_arc_vector outputs(const_iterator) const + throw(wrong_object,null_dereference,end_dereference); + arc_vector outputs(iterator) + throw(wrong_object,null_dereference,end_dereference); + + // find the output index of an arc which goes from this node + // returns digraph::npos if the arc is not an output of from + unsigned output_offset(const_iterator from, const_arc_iterator arc) const + throw(wrong_object,null_dereference,end_dereference); + unsigned output_offset(iterator from, arc_iterator arc) + throw(wrong_object,null_dereference,end_dereference); + // ditto for an input arc + unsigned input_offset(const_iterator to, const_arc_iterator arc) const + throw(wrong_object,null_dereference,end_dereference); + unsigned input_offset(iterator to, arc_iterator arc) + throw(wrong_object,null_dereference,end_dereference); + + ////////////////////////////////////////////////////////////////////////// + // Basic Arc functions + // to avoid name conflicts, arc functions have the arc_ prefix + // Arcs, like nodes, are referred to by a list iterator which is returned by the arc_insert function + // They may also be visited from arc_begin() to arc_end() + // Each arc has a from field and a to field which contain the node iterators of the endpoints of the arc + // Of course, the arc data can be accessed by simply dereferencing the iterator + + // tests for the number of arcs and the special test for zero arcs + bool arc_empty (void) const; + unsigned arc_size(void) const; + + // add a new arc and return its iterator + arc_iterator arc_insert(iterator from, iterator to, const AT& arc_data = AT()) + throw(wrong_object,null_dereference,end_dereference); + + // remove an arc and return the iterator to the next arc + arc_iterator arc_erase(arc_iterator) + throw(wrong_object,null_dereference,end_dereference); + // remove all arcs + void arc_clear(void); + + // traverse all the arcs in no particular order using STL-style iteration + const_arc_iterator arc_begin(void) const; + arc_iterator arc_begin(void); + const_arc_iterator arc_end(void) const; + arc_iterator arc_end(void); + + // find the node that an arc points from or to + const_iterator arc_from(const_arc_iterator) const + throw(wrong_object,null_dereference,end_dereference); + iterator arc_from(arc_iterator) + throw(wrong_object,null_dereference,end_dereference); + const_iterator arc_to(const_arc_iterator) const + throw(wrong_object,null_dereference,end_dereference); + iterator arc_to(arc_iterator) + throw(wrong_object,null_dereference,end_dereference); + + // reconnect an arc to a different from and to node + void arc_move(arc_iterator arc, iterator from, iterator to) + throw(wrong_object,null_dereference,end_dereference); + // reconnect just the from node + void arc_move_from(arc_iterator arc, iterator from) + throw(wrong_object,null_dereference,end_dereference); + // reconnect just the to node + void arc_move_to(arc_iterator arc, iterator to) + throw(wrong_object,null_dereference,end_dereference); + // reverse the arc direction so that to becomes from and vice-versa + void arc_flip(arc_iterator arc) + throw(wrong_object,null_dereference,end_dereference); + + //////////////////////////////////////////////////////////////////////////////// + // Adjacency algorithms + + // test whether the nodes are adjacent i.e. whether there is an arc going from from to to + bool adjacent(const_iterator from, const_iterator to) const + throw(wrong_object,null_dereference,end_dereference); + bool adjacent(iterator from, iterator to) + throw(wrong_object,null_dereference,end_dereference); + + // as above, but returns the arc that makes the nodes adjacent + // returns the first arc if there's more than one, returns arc_end() if there are none + const_arc_iterator adjacent_arc(const_iterator from, const_iterator to) const + throw(wrong_object,null_dereference,end_dereference); + arc_iterator adjacent_arc(iterator from, iterator to) + throw(wrong_object,null_dereference,end_dereference); + + // as above, but returns the set of all arcs that make two nodes adjacent (there may be more than one) + // returns an empty vector if there are none + const_arc_vector adjacent_arcs(const_iterator from, const_iterator to) const + throw(wrong_object,null_dereference,end_dereference); + arc_vector adjacent_arcs(iterator from, iterator to) + throw(wrong_object,null_dereference,end_dereference); + + // return the adjacency sets for the node inputs or outputs, i.e. the set of nodes adjacent to this node + // each adjacent node will only be entered once even if there are multiple arcs between the nodes + const_node_vector input_adjacencies(const_iterator to) const + throw(wrong_object,null_dereference,end_dereference); + node_vector input_adjacencies(iterator to) + throw(wrong_object,null_dereference,end_dereference); + const_node_vector output_adjacencies(const_iterator from) const + throw(wrong_object,null_dereference,end_dereference); + node_vector output_adjacencies(iterator from) + throw(wrong_object,null_dereference,end_dereference); + + //////////////////////////////////////////////////////////////////////////////// + // Topographical Sort Algorithm + // This generates a node ordering such that each node is visited after its fanin nodes. + + // This only generates a valid ordering for a DAG. + + // The return value is a pair : + // - the node vector which is a set of iterators to the nodes in sorted order + // - the arc vector is the set of backward ards that were broken to achieve the sort + // If the arc vector is empty then the graph formed a DAG. + + // The arc selection callback can be used to ignore arcs that are not part + // of the ordering, i.e. arcs that are meant to be backwards arcs + + std::pair sort(arc_select_fn = 0) const; + std::pair sort(arc_select_fn = 0); + + // Simplified variant of above for graphs that are known to be DAGs. + // If the sort fails due to backward arcs, the + // return vector is empty. Note that this will also be empty if the graph + // has no nodes in it, so use the empty() method to differentiate. + + const_node_vector dag_sort(arc_select_fn = 0) const; + node_vector dag_sort(arc_select_fn = 0); + + //////////////////////////////////////////////////////////////////////////////// + // Basic Path Algorithms + // A path is a series of arcs - you can use arc_from and arc_to to convert + // that into a series of nodes. All the path algorithms take an arc_select + // which allows arcs to be selected or rejected for consideration in a path. + + // A selection callback function is applied to each arc in the traversal and + // returns true if the arc is to be selected and false if the arc is to be + // rejected. If no function is provided the arc is selected. If you want to + // use arc selection you should create a function with the type profile given + // by the arc_select_fn type. The select function is passed both the graph and + // the arc iterator so that it is possible to select an arc on the basis of + // the nodes it is connected to. + + // Note: I used a callback because the STL-like predicate idea wasn't working for me... + + // test for the existence of a path from from to to + bool path_exists(const_iterator from, const_iterator to, arc_select_fn = 0) const + throw(wrong_object,null_dereference,end_dereference); + bool path_exists(iterator from, iterator to, arc_select_fn = 0) + throw(wrong_object,null_dereference,end_dereference); + + // get the set of all paths from from to to + const_path_vector all_paths(const_iterator from, const_iterator to, arc_select_fn = 0) const + throw(wrong_object,null_dereference,end_dereference); + path_vector all_paths(iterator from, iterator to, arc_select_fn = 0) + throw(wrong_object,null_dereference,end_dereference); + + // get the set of all nodes that can be reached by any path from from + const_node_vector reachable_nodes(const_iterator from, arc_select_fn = 0) const + throw(wrong_object,null_dereference,end_dereference); + node_vector reachable_nodes(iterator from, arc_select_fn = 0) + throw(wrong_object,null_dereference,end_dereference); + + // get the set of all nodes that can reach to to by any path + const_node_vector reaching_nodes(const_iterator to, arc_select_fn = 0) const + throw(wrong_object,null_dereference,end_dereference); + node_vector reaching_nodes(iterator to, arc_select_fn = 0) + throw(wrong_object,null_dereference,end_dereference); + + //////////////////////////////////////////////////////////////////////////////// + // Unweighted Shortest path algorithms + + // find the shortest path from from to to + // This is an unweighted shortest path algorithm, i.e. the weight of each + // arc is assumed to be 1, so just counts the number of arcs + // if there is more than one shortest path it returns the first one + // If there are no paths, returns an empty path + const_arc_vector shortest_path(const_iterator from, const_iterator to, arc_select_fn = 0) const + throw(wrong_object,null_dereference,end_dereference); + arc_vector shortest_path(iterator from, iterator to, arc_select_fn = 0) + throw(wrong_object,null_dereference,end_dereference); + + // find the set of shortest paths from from to any other node in the graph + // that is reachable (i.e. for which path_exists() is true) + // This is an unweighted shortest path, so just counts the number of arcs + // if there is more than one shortest path to a node it returns the first one + // If there are no paths, returns an empty list + const_path_vector shortest_paths(const_iterator from, arc_select_fn = 0) const + throw(wrong_object,null_dereference,end_dereference); + path_vector shortest_paths(iterator from, arc_select_fn = 0) + throw(wrong_object,null_dereference,end_dereference); + + private: + friend class digraph_iterator; + friend class digraph_iterator; + friend class digraph_arc_iterator; + friend class digraph_arc_iterator; + + typedef std::set const_iterator_set; + typedef TYPENAME const_iterator_set::iterator const_iterator_set_iterator; + + bool path_exists_r(const_iterator from, const_iterator to, const_iterator_set& visited, arc_select_fn) const + throw(wrong_object,null_dereference,end_dereference); + + void all_paths_r(const_iterator from, const_iterator to, const_arc_vector& so_far, const_path_vector& result, arc_select_fn) const + throw(wrong_object,null_dereference,end_dereference); + + void reachable_nodes_r(const_iterator from, const_iterator_set& visited, arc_select_fn) const + throw(wrong_object,null_dereference,end_dereference); + + void reaching_nodes_r(const_iterator to, const_iterator_set& visited, arc_select_fn) const + throw(wrong_object,null_dereference,end_dereference); + + digraph_node* m_nodes_begin; + digraph_node* m_nodes_end; + digraph_arc* m_arcs_begin; + digraph_arc* m_arcs_end; + }; + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#include "digraph.tpp" +#endif diff --git a/src/stlplus/containers/digraph.tpp b/src/stlplus/containers/digraph.tpp index 804954a..ea6f84e 100644 --- a/src/stlplus/containers/digraph.tpp +++ b/src/stlplus/containers/digraph.tpp @@ -1,1483 +1,1483 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Note: I tried to write this using STL lists for the node and arc lists, but -// it got far too hairy. The specific problem is that I wanted a digraph -// iterator to contain a list::iterator so I needed to be able to generate a -// list::iterator from a node or arc and STL list iterators don't give you that -// functionality. I tried burgling the data structures, but that was -// non-portable between different STL implementations so needed lots of #ifdefs -// and so was mind-bogglingly awful and unreadable - in other words a -// maintenance nightmare. I gave up and impemented my own lists - not difficult. - -// I use circular double-linked lists. The circular design means that both -// ends of the list are equally accessible in unit time. An empty list -// contains no objects. There is no end node in the list - unlike the STL -// lists which have a dummy node for end iterators to point to - -// conceptually the end iterator points one element beyond the end of the -// list. However, I implement the end iterator concept in the iterator -// itself, so do not need the dummy end node. - -//////////////////////////////////////////////////////////////////////////////// -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -// Internals - -namespace stlplus -{ - - template - class digraph_node - { - public: - master_iterator, digraph_node > m_master; - NT m_data; - digraph_node* m_prev; - digraph_node* m_next; - std::vector*> m_inputs; - std::vector*> m_outputs; - public: - digraph_node(const digraph* owner, const NT& d = NT()) : - m_master(owner,this), m_data(d), m_prev(0), m_next(0) - { - } - ~digraph_node(void) - { - } - }; - - template - class digraph_arc - { - public: - master_iterator, digraph_arc > m_master; - AT m_data; - digraph_arc* m_prev; - digraph_arc* m_next; - digraph_node* m_from; - digraph_node* m_to; - digraph_arc(const digraph* owner, digraph_node* from = 0, digraph_node* to = 0, const AT& d = AT()) : - m_master(owner,this), m_data(d), m_prev(0), m_next(0), m_from(from), m_to(to) - { - } - }; - - //////////////////////////////////////////////////////////////////////////////// - // Iterators - //////////////////////////////////////////////////////////////////////////////// - - //////////////////////////////////////////////////////////////////////////////// - // Node iterator - - // construct a null iterator - template - digraph_iterator::digraph_iterator(void) - { - } - - // valid iterator - template - digraph_iterator::digraph_iterator(digraph_node* node) : - safe_iterator,digraph_node >(node->m_master) - { - } - - // end iterator - template - digraph_iterator::digraph_iterator(const digraph* owner) : - safe_iterator,digraph_node >(owner) - { - } - - // alias an iterator - template - digraph_iterator::digraph_iterator(const safe_iterator, digraph_node >& iterator) : - safe_iterator,digraph_node >(iterator) - { - } - - // destructor - template - digraph_iterator::~digraph_iterator(void) - { - } - - template - TYPENAME digraph_iterator::const_iterator digraph_iterator::constify (void) const - { - return digraph_iterator(*this); - } - - template - TYPENAME digraph_iterator::iterator digraph_iterator::deconstify (void) const - { - return digraph_iterator(*this); - } - - template - TYPENAME digraph_iterator::this_iterator& digraph_iterator::operator ++ (void) - throw(null_dereference,end_dereference) - { - this->assert_valid(); - if (this->node()->m_next) - this->set(this->node()->m_next->m_master); - else - this->set_end(); - return *this; - } - - template - TYPENAME digraph_iterator::this_iterator digraph_iterator::operator ++ (int) - throw(null_dereference,end_dereference) - { - // post-increment is defined in terms of the pre-increment - digraph_iterator result(*this); - ++(*this); - return result; - } - - template - TYPENAME digraph_iterator::this_iterator& digraph_iterator::operator -- (void) - throw(null_dereference,end_dereference) - { - this->assert_valid(); - if (this->node()->m_prev) - this->set(this->node()->m_prev->m_master); - else - this->set_end(); - return *this; - } - - template - TYPENAME digraph_iterator::this_iterator digraph_iterator::operator -- (int) - throw(null_dereference,end_dereference) - { - // post-decrement is defined in terms of the pre-decrement - digraph_iterator result(*this); - --(*this); - return result; - } - - template - bool digraph_iterator::operator == (const TYPENAME digraph_iterator::this_iterator& r) const - { - return equal(r); - } - - template - bool digraph_iterator::operator != (const TYPENAME digraph_iterator::this_iterator& r) const - { - return !operator==(r); - } - - template - bool digraph_iterator::operator < (const TYPENAME digraph_iterator::this_iterator& r) const - { - return compare(r) < 0; - } - - template - TYPENAME digraph_iterator::reference digraph_iterator::operator*(void) const - throw(null_dereference,end_dereference) - { - this->assert_valid(); - return this->node()->m_data; - } - - template - TYPENAME digraph_iterator::pointer digraph_iterator::operator->(void) const - throw(null_dereference,end_dereference) - { - return &(operator*()); - } - - //////////////////////////////////////////////////////////////////////////////// - // Arc Iterator - - template - digraph_arc_iterator::digraph_arc_iterator(void) - { - } - - // valid iterator - template - digraph_arc_iterator::digraph_arc_iterator(digraph_arc* arc) : - safe_iterator,digraph_arc >(arc->m_master) - { - } - - // end iterator - template - digraph_arc_iterator::digraph_arc_iterator(const digraph* owner) : - safe_iterator,digraph_arc >(owner) - { - } - - // alias an iterator - template - digraph_arc_iterator::digraph_arc_iterator(const safe_iterator, digraph_arc >& iterator) : - safe_iterator,digraph_arc >(iterator) - { - } - - template - digraph_arc_iterator::~digraph_arc_iterator(void) - { - } - - template - TYPENAME digraph_arc_iterator::const_iterator digraph_arc_iterator::constify (void) const - { - return digraph_arc_iterator(*this); - } - - template - TYPENAME digraph_arc_iterator::iterator digraph_arc_iterator::deconstify (void) const - { - return digraph_arc_iterator(*this); - } - - template - TYPENAME digraph_arc_iterator::this_iterator& digraph_arc_iterator::operator ++ (void) - throw(null_dereference,end_dereference) - { - this->assert_valid(); - if (this->node()->m_next) - this->set(this->node()->m_next->m_master); - else - this->set_end(); - return *this; - } - - template - TYPENAME digraph_arc_iterator::this_iterator digraph_arc_iterator::operator ++ (int) - throw(null_dereference,end_dereference) - { - // post-increment is defined in terms of the pre-increment - digraph_arc_iterator result(*this); - ++(*this); - return result; - } - - template - TYPENAME digraph_arc_iterator::this_iterator& digraph_arc_iterator::operator -- (void) - throw(null_dereference,end_dereference) - { - this->assert_valid(); - if (this->node()->m_prev) - this->set(this->node()->m_prev->m_master); - else - this->set_end(); - return *this; - } - - template - TYPENAME digraph_arc_iterator::this_iterator digraph_arc_iterator::operator -- (int) - throw(null_dereference,end_dereference) - { - // post-decrement is defined in terms of the pre-decrement - digraph_arc_iterator result(*this); - --(*this); - return result; - } - - template - bool digraph_arc_iterator::operator == (const TYPENAME digraph_arc_iterator::this_iterator& r) const - { - return equal(r); - } - - template - bool digraph_arc_iterator::operator != (const TYPENAME digraph_arc_iterator::this_iterator& r) const - { - return !operator==(r); - } - - template - bool digraph_arc_iterator::operator < (const TYPENAME digraph_arc_iterator::this_iterator& r) const - { - return compare(r) < 0; - } - - template - TYPENAME digraph_arc_iterator::reference digraph_arc_iterator::operator*(void) const - throw(null_dereference,end_dereference) - { - this->assert_valid(); - return this->node()->m_data; - } - - template - TYPENAME digraph_arc_iterator::pointer digraph_arc_iterator::operator->(void) const - throw(null_dereference,end_dereference) - { - return &(operator*()); - } - - //////////////////////////////////////////////////////////////////////////////// - // subtype utilities - - template - TYPENAME digraph::const_arc_vector digraph::constify_arcs(const TYPENAME digraph::arc_vector& arcs) const - throw(wrong_object,null_dereference,end_dereference) - { - std::vector > result; - for (unsigned i = 0; i < arcs.size(); i++) - { - arcs[i].assert_valid(this); - result.push_back(arcs[i].constify()); - } - return result; - } - - template - TYPENAME digraph::arc_vector digraph::deconstify_arcs(const TYPENAME digraph::const_arc_vector& arcs) const - throw(wrong_object,null_dereference,end_dereference) - { - std::vector > result; - for (unsigned i = 0; i < arcs.size(); i++) - { - arcs[i].assert_valid(this); - result.push_back(arcs[i].deconstify()); - } - return result; - } - - template - TYPENAME digraph::const_path_vector digraph::constify_paths(const TYPENAME digraph::path_vector& paths) const - throw(wrong_object,null_dereference,end_dereference) - { - std::vector > > result; - for (unsigned i = 0; i < paths.size(); i++) - result.push_back(constify_arcs(paths[i])); - return result; - } - - template - TYPENAME digraph::path_vector digraph::deconstify_paths(const TYPENAME digraph::const_path_vector& paths) const - throw(wrong_object,null_dereference,end_dereference) - { - std::vector > > result; - for (unsigned i = 0; i < paths.size(); i++) - result.push_back(deconstify_arcs(paths[i])); - return result; - } - - template - TYPENAME digraph::const_node_vector digraph::constify_nodes(const TYPENAME digraph::node_vector& nodes) const - throw(wrong_object,null_dereference,end_dereference) - { - std::vector > result; - for (unsigned i = 0; i < nodes.size(); i++) - { - nodes[i].assert_valid(this); - result.push_back(nodes[i].constify()); - } - return result; - } - - template - TYPENAME digraph::node_vector digraph::deconstify_nodes(const TYPENAME digraph::const_node_vector& nodes) const - throw(wrong_object,null_dereference,end_dereference) - { - std::vector > result; - for (unsigned i = 0; i < nodes.size(); i++) - { - nodes[i].assert_valid(this); - result.push_back(nodes[i].deconstify()); - } - return result; - } - - template - unsigned digraph::npos(void) - { - return(unsigned)-1; - } - - //////////////////////////////////////////////////////////////////////////////// - // Constructors etc. - - template - digraph::digraph(void) : - m_nodes_begin(0), m_nodes_end(0), m_arcs_begin(0), m_arcs_end(0) - { - // node and arc lists are circular double-linked lists - // they start out empty (no dummy end node) - } - - template - digraph::~digraph(void) - { - clear(); - } - - template - digraph::digraph(const digraph& r) : - m_nodes_begin(0), m_nodes_end(0), m_arcs_begin(0), m_arcs_end(0) - { - *this = r; - } - - template - digraph& digraph::operator=(const digraph& r) - { - // make it self-copy safe i.e. a=a; is a valid instruction - if (this == &r) return *this; - clear(); - // first phase is to copy the nodes, creating a map of cross references from the old nodes to their new equivalents - std::map, digraph_iterator > xref; - for (digraph_iterator n = r.begin(); n != r.end(); n++) - xref[n] = insert(*n); - // second phase is to copy the arcs, using the map to convert the old to and from nodes to the new nodes - for (digraph_arc_iterator a = r.arc_begin(); a != r.arc_end(); a++) - arc_insert(xref[r.arc_from(a)],xref[r.arc_to(a)],*a); - return *this; - } - - //////////////////////////////////////////////////////////////////////////////// - // Basic Node functions - - template - bool digraph::empty(void) const - { - return m_nodes_begin == 0; - } - - template - unsigned digraph::size(void) const - { - unsigned count = 0; - for (digraph_iterator i = begin(); i != end(); i++) - count++; - return count; - } - - template - TYPENAME digraph::iterator digraph::insert(const NT& node_data) - { - digraph_node* new_node = new digraph_node(this,node_data); - if (!m_nodes_end) - { - // insert into an empty list - m_nodes_begin = new_node; - m_nodes_end = new_node; - } - else - { - // insert at the end of the list - new_node->m_prev = m_nodes_end; - m_nodes_end->m_next = new_node; - m_nodes_end = new_node; - } - return digraph_iterator(new_node); - } - - template - TYPENAME digraph::iterator digraph::erase(TYPENAME digraph::iterator iter) - throw(wrong_object,null_dereference,end_dereference) - { - iter.assert_valid(this); - // remove all arcs connected to this node first - // use arc_erase rather than arcs.erase because that tidies up the node at the other end of the arc too - for (unsigned i = fanin(iter); i--; ) - arc_erase(input(iter,i)); - for (unsigned j = fanout(iter); j--; ) - arc_erase(output(iter,j)); - // now unlink the node from the list and delete it - if (iter.node()->m_next) - iter.node()->m_next->m_prev = iter.node()->m_prev; - if (iter.node()->m_prev) - iter.node()->m_prev->m_next = iter.node()->m_next; - digraph_node* next = iter.node()->m_next; - delete iter.node(); - // return the next node in the list - if (next) - return digraph_iterator(next); - else - return digraph_iterator(this); - } - - template - void digraph::clear(void) - { - // clearing the nodes also clears the arcs - for (digraph_iterator i = begin(); i != end(); ) - i = erase(i); - } - - template - TYPENAME digraph::const_iterator digraph::begin(void) const - { - if (m_nodes_begin) - return digraph_iterator(m_nodes_begin); - else - return digraph_iterator(this); - } - - template - TYPENAME digraph::iterator digraph::begin(void) - { - if (m_nodes_begin) - return digraph_iterator(m_nodes_begin); - else - return digraph_iterator(this); - } - - template - TYPENAME digraph::const_iterator digraph::end(void) const - { - return digraph_iterator(this); - } - - template - TYPENAME digraph::iterator digraph::end(void) - { - return digraph_iterator(this); - } - - template - unsigned digraph::fanin(TYPENAME digraph::const_iterator iter) const - throw(wrong_object,null_dereference,end_dereference) - { - iter.assert_valid(this); - return iter.node()->m_inputs.size(); - } - - template - unsigned digraph::fanin(TYPENAME digraph::iterator iter) - throw(wrong_object,null_dereference,end_dereference) - { - iter.assert_valid(this); - return iter.node()->m_inputs.size(); - } - - template - TYPENAME digraph::const_arc_iterator digraph::input(TYPENAME digraph::const_iterator iter, unsigned i) const - throw(wrong_object,null_dereference,end_dereference,std::out_of_range) - { - iter.assert_valid(this); - if (i >= iter.node()->m_inputs.size()) throw std::out_of_range("digraph::input"); - return digraph_arc_iterator(iter.node()->m_inputs[i]); - } - - template - TYPENAME digraph::arc_iterator digraph::input(TYPENAME digraph::iterator iter, unsigned i) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range) - { - iter.assert_valid(this); - if (i >= iter.node()->m_inputs.size()) throw std::out_of_range("digraph::input"); - return digraph_arc_iterator(iter.node()->m_inputs[i]); - } - - template - unsigned digraph::fanout(TYPENAME digraph::const_iterator iter) const - throw(wrong_object,null_dereference,end_dereference) - { - iter.assert_valid(this); - return iter.node()->m_outputs.size(); - } - - template - unsigned digraph::fanout(TYPENAME digraph::iterator iter) - throw(wrong_object,null_dereference,end_dereference) - { - iter.assert_valid(this); - return iter.node()->m_outputs.size(); - } - - template - TYPENAME digraph::const_arc_iterator digraph::output(TYPENAME digraph::const_iterator iter, unsigned i) const - throw(wrong_object,null_dereference,end_dereference,std::out_of_range) - { - iter.assert_valid(this); - if (i >= iter.node()->m_outputs.size()) throw std::out_of_range("digraph::output"); - return digraph_arc_iterator(iter.node()->m_outputs[i]); - } - - template - TYPENAME digraph::arc_iterator digraph::output(TYPENAME digraph::iterator iter, unsigned i) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range) - { - iter.assert_valid(this); - if (i >= iter.node()->m_outputs.size()) throw std::out_of_range("digraph::output"); - return digraph_arc_iterator(iter.node()->m_outputs[i]); - } - - template - TYPENAME digraph::const_arc_vector digraph::inputs(TYPENAME digraph::const_iterator node) const - throw(wrong_object,null_dereference,end_dereference) - { - node.assert_valid(this); - std::vector > result; - for (unsigned i = 0; i < fanin(node); i++) - result.push_back(input(node,i)); - return result; - } - - template - TYPENAME digraph::arc_vector digraph::inputs(TYPENAME digraph::iterator node) - throw(wrong_object,null_dereference,end_dereference) - { - node.assert_valid(this); - std::vector > result; - for (unsigned i = 0; i < fanin(node); i++) - result.push_back(input(node,i)); - return result; - } - - template - TYPENAME digraph::const_arc_vector digraph::outputs(TYPENAME digraph::const_iterator node) const - throw(wrong_object,null_dereference,end_dereference) - { - node.assert_valid(this); - std::vector > result; - for (unsigned i = 0; i < fanout(node); i++) - result.push_back(output(node,i)); - return result; - } - - template - TYPENAME digraph::arc_vector digraph::outputs(TYPENAME digraph::iterator node) - throw(wrong_object,null_dereference,end_dereference) - { - node.assert_valid(this); - std::vector > result; - for (unsigned i = 0; i < fanout(node); i++) - result.push_back(output(node,i)); - return result; - } - - template - unsigned digraph::output_offset(TYPENAME digraph::const_iterator from, - TYPENAME digraph::const_arc_iterator arc) const - throw(wrong_object,null_dereference,end_dereference) - { - from.assert_valid(this); - arc.assert_valid(this); - for (unsigned i = 0; i < fanout(from); i++) - { - if (output(from,i) == arc) - return i; - } - return digraph::npos(); - } - - template - unsigned digraph::output_offset(TYPENAME digraph::iterator from, - TYPENAME digraph::arc_iterator arc) - throw(wrong_object,null_dereference,end_dereference) - { - from.assert_valid(this); - arc.assert_valid(this); - for (unsigned i = 0; i < fanout(from); i++) - { - if (output(from,i) == arc) - return i; - } - return digraph::npos(); - } - - template - unsigned digraph::input_offset(TYPENAME digraph::const_iterator to, - TYPENAME digraph::const_arc_iterator arc) const - throw(wrong_object,null_dereference,end_dereference) - { - to.assert_valid(this); - arc.assert_valid(this); - for (unsigned i = 0; i < fanin(to); i++) - { - if (input(to,i) == arc) - return i; - } - return digraph::npos(); - } - - template - unsigned digraph::input_offset(TYPENAME digraph::iterator to, - TYPENAME digraph::arc_iterator arc) - throw(wrong_object,null_dereference,end_dereference) - { - to.assert_valid(this); - arc.assert_valid(this); - for (unsigned i = 0; i < fanin(to); i++) - { - if (input(to,i) == arc) - return i; - } - return digraph::npos(); - } - - //////////////////////////////////////////////////////////////////////////////// - // Basic Arc functions - - template - bool digraph::arc_empty(void) const - { - return m_arcs_end == 0; - } - - template - unsigned digraph::arc_size(void) const - { - unsigned count = 0; - for (digraph_arc_iterator i = arc_begin(); i != arc_end(); i++) - count++; - return count; - } - - template - TYPENAME digraph::arc_iterator digraph::arc_insert(TYPENAME digraph::iterator from, - TYPENAME digraph::iterator to, - const AT& arc_data) - throw(wrong_object,null_dereference,end_dereference) - { - from.assert_valid(this); - to.assert_valid(this); - // create the new arc and link it in to the arc list - digraph_arc* new_arc = new digraph_arc(this, from.node(), to.node(), arc_data); - if (!m_arcs_end) - { - // insert into an empty list - m_arcs_begin = new_arc; - m_arcs_end = new_arc; - } - else - { - // insert at the end of the list - new_arc->m_prev = m_arcs_end; - m_arcs_end->m_next = new_arc; - m_arcs_end = new_arc; - } - // add this arc to the inputs and outputs of the end nodes - from.node()->m_outputs.push_back(new_arc); - to.node()->m_inputs.push_back(new_arc); - return digraph_arc_iterator(new_arc); - } - - template - TYPENAME digraph::arc_iterator digraph::arc_erase(TYPENAME digraph::arc_iterator iter) - throw(wrong_object,null_dereference,end_dereference) - { - iter.assert_valid(this); - // first remove this arc's pointers from the from/to nodes - for (TYPENAME std::vector*>::iterator i = iter.node()->m_to->m_inputs.begin(); i != iter.node()->m_to->m_inputs.end(); ) - { - if (*i == iter.node()) - i = iter.node()->m_to->m_inputs.erase(i); - else - i++; - } - for (TYPENAME std::vector*>::iterator o = iter.node()->m_from->m_outputs.begin(); o != iter.node()->m_from->m_outputs.end(); ) - { - if (*o == iter.node()) - o = iter.node()->m_from->m_outputs.erase(o); - else - o++; - } - // now unlink the arc from the list and delete it - if (iter.node()->m_next) - iter.node()->m_next->m_prev = iter.node()->m_prev; - if (iter.node()->m_prev) - iter.node()->m_prev->m_next = iter.node()->m_next; - digraph_arc* next = iter.node()->m_next; - delete iter.node(); - if (next) - return digraph_arc_iterator(next); - else - return digraph_arc_iterator(this); - } - - template - void digraph::arc_clear(void) - { - for (digraph_arc_iterator a = arc_begin(); a != arc_end(); ) - a = arc_erase(a); - } - - template - TYPENAME digraph::const_arc_iterator digraph::arc_begin(void) const - { - if (m_arcs_begin) - return digraph_arc_iterator(m_arcs_begin); - else - return digraph_arc_iterator(this); - } - - template - TYPENAME digraph::arc_iterator digraph::arc_begin(void) - { - if (m_arcs_begin) - return digraph_arc_iterator(m_arcs_begin); - else - return digraph_arc_iterator(this); - } - - template - TYPENAME digraph::const_arc_iterator digraph::arc_end(void) const - { - return digraph_arc_iterator(this); - } - - template - TYPENAME digraph::arc_iterator digraph::arc_end(void) - { - return digraph_arc_iterator(this); - } - - template - TYPENAME digraph::const_iterator digraph::arc_from(TYPENAME digraph::const_arc_iterator iter) const - throw(wrong_object,null_dereference,end_dereference) - { - iter.assert_valid(this); - return digraph_iterator(iter.node()->m_from); - } - - template - TYPENAME digraph::iterator digraph::arc_from(TYPENAME digraph::arc_iterator iter) - throw(wrong_object,null_dereference,end_dereference) - { - iter.assert_valid(this); - return digraph_iterator(iter.node()->m_from); - } - - template - TYPENAME digraph::const_iterator digraph::arc_to(TYPENAME digraph::const_arc_iterator iter) const - throw(wrong_object,null_dereference,end_dereference) - { - iter.assert_valid(this); - return digraph_iterator(iter.node()->m_to); - } - - template - TYPENAME digraph::iterator digraph::arc_to(TYPENAME digraph::arc_iterator iter) - throw(wrong_object,null_dereference,end_dereference) - { - iter.assert_valid(this); - return digraph_iterator(iter.node()->m_to); - } - - template - void digraph::arc_move(TYPENAME digraph::arc_iterator arc, - TYPENAME digraph::iterator from, - TYPENAME digraph::iterator to) - throw(wrong_object,null_dereference,end_dereference) - { - arc_move_to(arc,to); - arc_move_from(arc,from); - } - - template - void digraph::arc_move_from(TYPENAME digraph::arc_iterator arc, - TYPENAME digraph::iterator from) - throw(wrong_object,null_dereference,end_dereference) - { - arc.assert_valid(this); - from.assert_valid(this); - for (TYPENAME std::vector*>::iterator o = arc.node()->m_from->m_outputs.begin(); o != arc.node()->m_from->m_outputs.end(); ) - { - if (*o == arc.node()) - o = arc.node()->m_from->m_outputs.erase(o); - else - o++; - } - from.node()->m_outputs.push_back(arc.node()); - arc.node()->m_from = from.node(); - } - - template - void digraph::arc_move_to(TYPENAME digraph::arc_iterator arc, - TYPENAME digraph::iterator to) - throw(wrong_object,null_dereference,end_dereference) - { - arc.assert_valid(this); - to.assert_valid(this); - for (TYPENAME std::vector*>::iterator i = arc.node()->m_to->m_inputs.begin(); i != arc.node()->m_to->m_inputs.end(); ) - { - if (*i == arc.node()) - i = arc.node()->m_to->m_inputs.erase(i); - else - i++; - } - to.node()->m_inputs.push_back(arc.node()); - arc.node()->m_to = to.node(); - } - - template - void digraph::arc_flip(TYPENAME digraph::arc_iterator arc) - throw(wrong_object,null_dereference,end_dereference) - { - arc_move(arc,arc_to(arc),arc_from(arc)); - } - - //////////////////////////////////////////////////////////////////////////////// - // Adjacency Algorithms - - template - bool digraph::adjacent(TYPENAME digraph::const_iterator from, - TYPENAME digraph::const_iterator to) const - throw(wrong_object,null_dereference,end_dereference) - { - return adjacent_arc(from,to) != arc_end(); - } - - template - bool digraph::adjacent(TYPENAME digraph::iterator from, - TYPENAME digraph::iterator to) - throw(wrong_object,null_dereference,end_dereference) - { - return adjacent_arc(from,to) != arc_end(); - } - - template - TYPENAME digraph::const_arc_iterator digraph::adjacent_arc(TYPENAME digraph::const_iterator from, - TYPENAME digraph::const_iterator to) const - throw(wrong_object,null_dereference,end_dereference) - { - from.assert_valid(this); - to.assert_valid(this); - for (unsigned arc = 0; arc < fanout(from); arc++) - { - if (arc_to(output(from, arc)) == to) - return output(from,arc); - } - return arc_end(); - } - - template - TYPENAME digraph::arc_iterator digraph::adjacent_arc(TYPENAME digraph::iterator from, - TYPENAME digraph::iterator to) - throw(wrong_object,null_dereference,end_dereference) - { - return adjacent_arc(from.constify(), to.constify()).deconstify(); - } - - template - TYPENAME digraph::const_arc_vector digraph::adjacent_arcs(TYPENAME digraph::const_iterator from, - TYPENAME digraph::const_iterator to) const - throw(wrong_object,null_dereference,end_dereference) - { - from.assert_valid(this); - to.assert_valid(this); - std::vector > result; - for (unsigned arc = 0; arc < fanout(from); arc++) - { - if (arc_to(output(from, arc)) == to) - result.push_back(output(from,arc)); - } - return result; - } - - template - TYPENAME digraph::arc_vector digraph::adjacent_arcs(TYPENAME digraph::iterator from, - TYPENAME digraph::iterator to) - throw(wrong_object,null_dereference,end_dereference) - { - return deconstify_arcs(adjacent_arcs(from.constify(), to.constify())); - } - - template - TYPENAME digraph::const_node_vector digraph::input_adjacencies(TYPENAME digraph::const_iterator to) const - throw(wrong_object,null_dereference,end_dereference) - { - std::vector > result; - for (unsigned arc = 0; arc < fanin(to); arc++) - { - digraph_iterator from = arc_from(input(to, arc)); - if (std::find(result.begin(), result.end(), from) == result.end()) - result.push_back(from); - } - return result; - } - - template - TYPENAME digraph::node_vector digraph::input_adjacencies(TYPENAME digraph::iterator to) - throw(wrong_object,null_dereference,end_dereference) - { - return deconstify_nodes(input_adjacencies(to.constify())); - } - - template - TYPENAME digraph::const_node_vector digraph::output_adjacencies(TYPENAME digraph::const_iterator from) const - throw(wrong_object,null_dereference,end_dereference) - { - std::vector > result; - for (unsigned arc = 0; arc < fanout(from); arc++) - { - digraph_iterator to = arc_to(output(from, arc)); - if (find(result.begin(), result.end(), to) == result.end()) - result.push_back(to); - } - return result; - } - - template - TYPENAME digraph::node_vector digraph::output_adjacencies(TYPENAME digraph::iterator from) - throw(wrong_object,null_dereference,end_dereference) - { - return deconstify_nodes(output_adjacencies(from.constify())); - } - - //////////////////////////////////////////////////////////////////////////////// - // Topographical Sort Algorithms - - template - std::pair::const_node_vector, TYPENAME digraph::const_arc_vector> - digraph::sort(TYPENAME digraph::arc_select_fn select) const - { - std::vector > result; - std::vector > errors; - // build a map containing the number of fanins to each node that must be visited before this one - std::map,unsigned> fanin_map; - for (digraph_iterator n = begin(); n != end(); n++) - { - unsigned predecessors = 0; - // only count predecessors connected by selected arcs - for (unsigned f = 0; f < fanin(n); f++) - { - digraph_arc_iterator input_arc = input(n,f); - digraph_iterator predecessor = arc_from(input_arc); - if (!select || select(*this,input_arc)) - predecessors++; - } - if (predecessors == 0) - { - result.push_back(n); - } - else - { - fanin_map[n] = predecessors; - } - } - // main algorithm applies the topographical sort repeatedly. For a DAG, it - // will complete first time. However, with backward arcs, the first - // iteration will fail. The algorithm then tries breaking random arcs to try - // to get an ordering. - for(unsigned i = 0; !fanin_map.empty(); ) - { - // now visit each node in traversal order, decrementing the fanin count of - // all successors. As each successor's fanin count goes to zero, it is - // appended to the result. - for (; i < result.size(); i++) - { - // Note: dereferencing gives us a node iterator - digraph_iterator current = result[i]; - for (unsigned f = 0; f < fanout(current); f++) - { - // only consider successors connected by selected arcs - digraph_arc_iterator output_arc = output(current, f); - digraph_iterator successor = arc_to(output_arc); - if (!select || select(*this,output_arc)) - { - // don't consider arcs that have been eliminated to break a loop - if (fanin_map.find(successor) != fanin_map.end()) - { - --fanin_map[successor]; - if ((fanin_map[successor]) == 0) - { - result.push_back(successor); - fanin_map.erase(fanin_map.find(successor)); - } - } - } - } - } - if (!fanin_map.empty()) - { - // there must be backward arcs preventing completion - // try removing arcs from the sort to get a partial ordering containing all the nodes - - // select an arc that is still relevant to the sort and break it - // first select a node that has non-zero fanin and its predecessor that has non-zero fanin - digraph_iterator stuck_node = fanin_map.begin()->first; - for (unsigned f = 0; f < fanin(stuck_node); f++) - { - // now successively remove input arcs that are still part of the sort until the fanin reduces to zero - // first find a relevant arc - this must be a selected arc that has not yet been traversed by the first half of the algorithm - digraph_arc_iterator input_arc = input(stuck_node, f); - if (!select || select(*this,input_arc)) - { - digraph_iterator predecessor = arc_from(input_arc); - if (fanin_map.find(predecessor) != fanin_map.end()) - { - // found the right combination - remove this arc and then drop out of the fanin loop to restart the outer sort loop - errors.push_back(input_arc); - --fanin_map[stuck_node]; - if ((fanin_map[stuck_node]) == 0) - { - result.push_back(stuck_node); - fanin_map.erase(fanin_map.find(stuck_node)); - break; - } - } - } - } - } - } - return std::make_pair(result,errors); - } - - template - std::pair::node_vector, TYPENAME digraph::arc_vector> - digraph::sort(TYPENAME digraph::arc_select_fn select) - { - std::pair >, - std::vector > > const_result = - const_cast*>(this)->sort(select); - - std::pair >, - std::vector > > result = - std::make_pair(deconstify_nodes(const_result.first),deconstify_arcs(const_result.second)); - return result; - } - - template - TYPENAME digraph::const_node_vector digraph::dag_sort(TYPENAME digraph::arc_select_fn select) const - { - std::pair >, - std::vector > > result = sort(select); - if (result.second.empty()) return result.first; - return std::vector >(); - } - - template - TYPENAME digraph::node_vector digraph::dag_sort(TYPENAME digraph::arc_select_fn select) - { - return deconstify_nodes(const_cast*>(this)->dag_sort(select)); - } - //////////////////////////////////////////////////////////////////////////////// - // Path Algorithms - - template - bool digraph::path_exists_r(TYPENAME digraph::const_iterator from, - TYPENAME digraph::const_iterator to, - TYPENAME digraph::const_iterator_set& visited, - TYPENAME digraph::arc_select_fn select) const - throw(wrong_object,null_dereference,end_dereference) - { - // Recursive part of the digraph::path_exists function. This is based on a - // depth first search algorithm and stops the moment it finds a path - // regardless of its length. Simply traverse every output and recurse on that - // node until we find the to node or run out of things to recurse on. However, - // to avoid infinite recursion due to cycles in the graph, I need to maintain - // a set of visited nodes. The visited set is updated when a candidate is - // found but tested before the recursion on the candidate so that the number of - // function calls is minimised. - for (unsigned i = 0; i < fanout(from); i++) - { - digraph_arc_iterator arc = output(from,i); - if (!select || select(*this, arc)) - { - digraph_iterator node = arc_to(arc); - // if the node is the target, return immediately - if (node == to) return true; - // update the visited set and give up if the insert fails, which indicates that the node has already been visited - if (!(visited.insert(node).second)) return false; - // now recurse - a path exists from from to to if a path exists from an adjacent node to to - if (path_exists_r(node,to,visited,select)) return true; - } - } - return false; - } - - template - bool digraph::path_exists(TYPENAME digraph::const_iterator from, - TYPENAME digraph::const_iterator to, - TYPENAME digraph::arc_select_fn select) const - throw(wrong_object,null_dereference,end_dereference) - { - // set up the recursion with its initial visited set and then recurse - std::set > visited; - visited.insert(from); - return path_exists_r(from, to, visited, select); - } - - template - bool digraph::path_exists(TYPENAME digraph::iterator from, - TYPENAME digraph::iterator to, - TYPENAME digraph::arc_select_fn select) - throw(wrong_object,null_dereference,end_dereference) - { - return path_exists(from.constify(), to.constify(), select); - } - - template - void digraph::all_paths_r(TYPENAME digraph::const_iterator from, - TYPENAME digraph::const_iterator to, - TYPENAME digraph::const_arc_vector& so_far, - TYPENAME digraph::const_path_vector& result, - TYPENAME digraph::arc_select_fn select) const - throw(wrong_object,null_dereference,end_dereference) - { - // This is the recursive part of the all_paths function. The field so_far - // contains the path so far so that when 'to' is reached, the path is - // complete. It serves the same purpose as the visited set in the path_exists - // function except that it also preserves the path order. It also serves the - // purpose of detecting cycles and thus stopping infinite recursion. Every - // time the recursion reaches the to node, a copy of so_far is appended to the - // path set. - for (unsigned i = 0; i < fanout(from); i++) - { - digraph_arc_iterator candidate = output(from,i); - // assert_valid that the arc is selected and then assert_valid that the candidate has not - // been visited on this path and only allow further recursion if it hasn't - if ((!select || select(*this, candidate)) && std::find(so_far.begin(), so_far.end(), candidate) == so_far.end()) - { - // extend the path tracing the route to this arc - so_far.push_back(candidate); - // if the candidate arc points to the target, update the result set and prevent further recursion, otherwise recurse - if (arc_to(candidate) == to) - result.push_back(so_far); - else - all_paths_r(arc_to(candidate),to,so_far,result,select); - so_far.pop_back(); - } - } - } - - template - TYPENAME digraph::const_path_vector - digraph::all_paths(TYPENAME digraph::const_iterator from, - TYPENAME digraph::const_iterator to, - TYPENAME digraph::arc_select_fn select) const - throw(wrong_object,null_dereference,end_dereference) - { - // set up the recursion with empty data fields and then recurse - std::vector > > result; - std::vector > so_far; - all_paths_r(from, to, so_far, result, select); - return result; - } - - template - TYPENAME digraph::path_vector - digraph::all_paths(TYPENAME digraph::iterator from, - TYPENAME digraph::iterator to, - TYPENAME digraph::arc_select_fn select) - throw(wrong_object,null_dereference,end_dereference) - { - return deconstify_paths(all_paths(from.constify(), to.constify(), select)); - } - - template - void digraph::reachable_nodes_r(TYPENAME digraph::const_iterator from, - TYPENAME digraph::const_iterator_set& visited, - TYPENAME digraph::arc_select_fn select) const - throw(wrong_object,null_dereference,end_dereference) - { - // The recursive part of the reachable_nodes function. - // This is a depth-first traversal again but this time it carries on to find all the reachable nodes - // Just keep recursing on all the adjacent nodes of each node, skipping already visited nodes to avoid cycles - for (unsigned i = 0; i < fanout(from); i++) - { - digraph_arc_iterator arc = output(from,i); - if (!select || select(*this,arc)) - { - digraph_iterator candidate = arc_to(arc); - if (visited.insert(candidate).second) - reachable_nodes_r(candidate,visited,select); - } - } - } - - template - TYPENAME digraph::const_node_vector - digraph::reachable_nodes(TYPENAME digraph::const_iterator from, - TYPENAME digraph::arc_select_fn select) const - throw(wrong_object,null_dereference,end_dereference) - { - // seed the recursion, marking the starting node as already visited - std::set > visited; - visited.insert(from); - reachable_nodes_r(from, visited, select); - // convert the visited set into the required output form - // exclude the starting node - std::vector > result; - for (TYPENAME std::set >::iterator i = visited.begin(); i != visited.end(); i++) - if (*i != from) - result.push_back(*i); - return result; - } - - template - TYPENAME digraph::node_vector - digraph::reachable_nodes(TYPENAME digraph::iterator from, - TYPENAME digraph::arc_select_fn select) - throw(wrong_object,null_dereference,end_dereference) - { - return deconstify_nodes(reachable_nodes(from.constify(), select)); - } - - template - void digraph::reaching_nodes_r(TYPENAME digraph::const_iterator to, - TYPENAME digraph::const_iterator_set& visited, - TYPENAME digraph::arc_select_fn select) const - throw(wrong_object,null_dereference,end_dereference) - { - // The recursive part of the reaching_nodes function. - // Just like the reachable_nodes_r function but it goes backwards - for (unsigned i = 0; i < fanin(to); i++) - { - digraph_arc_iterator arc = input(to,i); - if (!select || select(*this,arc)) - { - digraph_iterator candidate = arc_from(input(to,i)); - if (visited.insert(candidate).second) - reaching_nodes_r(candidate,visited,select); - } - } - } - - template - TYPENAME digraph::const_node_vector - digraph::reaching_nodes(TYPENAME digraph::const_iterator to, - TYPENAME digraph::arc_select_fn select) const - throw(wrong_object,null_dereference,end_dereference) - { - // seed the recursion, marking the starting node as already visited - std::set > visited; - visited.insert(to); - reaching_nodes_r(to,visited,select); - // convert the visited set into the required output form - // exclude the end node - std::vector > result; - for (TYPENAME std::set >::iterator i = visited.begin(); i != visited.end(); i++) - if (*i != to) - result.push_back(*i); - return result; - } - - template - TYPENAME digraph::node_vector - digraph::reaching_nodes(TYPENAME digraph::iterator to, - TYPENAME digraph::arc_select_fn select) - throw(wrong_object,null_dereference,end_dereference) - { - return deconstify_nodes(reaching_nodes(to.constify(),select)); - } - - //////////////////////////////////////////////////////////////////////////////// - // Shortest Path Algorithms - - template - TYPENAME digraph::const_arc_vector - digraph::shortest_path(TYPENAME digraph::const_iterator from, - TYPENAME digraph::const_iterator to, - TYPENAME digraph::arc_select_fn select) const - throw(wrong_object,null_dereference,end_dereference) - { - std::vector > > paths = all_paths(from,to,select); - std::vector > shortest; - for (TYPENAME std::vector > >::iterator i = paths.begin(); i != paths.end(); i++) - if (shortest.empty() || i->size() < shortest.size()) - shortest = *i; - return shortest; - } - - template - TYPENAME digraph::arc_vector - digraph::shortest_path(TYPENAME digraph::iterator from, - TYPENAME digraph::iterator to, - TYPENAME digraph::arc_select_fn select) - throw(wrong_object,null_dereference,end_dereference) - { - return deconstify_arcs(shortest_path(from.constify(),to.constify(),select)); - } - - template - TYPENAME digraph::const_path_vector - digraph::shortest_paths(TYPENAME digraph::const_iterator from, - TYPENAME digraph::arc_select_fn select) const - throw(wrong_object,null_dereference,end_dereference) - { - from.assert_valid(this); - // This is an unweighted shortest path algorithm based on the algorithm from - // Weiss's book. This is essentially a breadth-first traversal or graph - // colouring algorithm. It is an iterative algorithm, so no recursion here! It - // works by creating a node queue initialised with the starting node. It then - // consumes the queue from front to back. For each node, it finds the - // successors and appends them to the queue. If a node is already 'known' it - // is not added - this avoids cycles. Thus the queue insert ordering - // represents the breadth-first ordering. On the way it creates a map of - // visited nodes. This is a map not a set because it also stores the arc that - // nominated this node as a shortest path. The full path can then be recreated - // from the map by just walking back through the predecessors. The depth (or - // colour) can be determined by the path length. - std::vector > > result; - // initialise the iteration by creating a queue and adding the start node - std::deque > nodes; - nodes.push_back(from); - // Create a map to store the set of known nodes mapped to their predecessor - // arcs. Initialise it with the current node, which has no predecessor. Note - // that the algorithm uses the feature of digraph iterators that they can be - // null iterators and that all null iterators are equal. - typedef std::map, - digraph_arc_iterator > known_map; - known_map known; - known.insert(std::make_pair(from,digraph_arc_iterator())); - // now the iterative part of the algorithm - while(!nodes.empty()) - { - // pop the queue to get the next node to process - unfortunately the STL - // deque::pop does not return the popped value - digraph_iterator current = nodes.front(); - nodes.pop_front(); - // now visit all the successors - for (unsigned i = 0; i < fanout(current); i++) - { - digraph_arc_iterator next_arc = output(current,i); - // assert_valid whether the successor arc is a selected arc and can be part of a path - if (!select || select(*this,next_arc)) - { - digraph_iterator next = arc_to(next_arc); - // Discard any successors that are known because to be known already they - // must have another shorter path. Otherwise add the successor node to the - // queue to be visited later. To minimise the overhead of map lookup I use - // the usual trick of trying to insert the node and determining whether - // the node was known by the success or failure of the insertion - this is - // a Good STL Trick (TM). - if (known.insert(std::make_pair(next,next_arc)).second) - nodes.push_back(next); - } - } - } - // The map contains the results as an unordered set of nodes, mapped to their - // predecessor arcs and weight. This now needs to be converted into a set of - // paths. This is done by starting with a node from the map, finding its - // predecessor arc and therefore its predecessor node, looking that up in the - // map to find its predecessor and so on until the start node is reached (it - // has a null predecessor). Note that the known set includes the from node - // which does not generate a path. - for (TYPENAME known_map::iterator i = known.begin(); i != known.end(); i++) - { - if (i->first != from) - { - const_arc_vector this_path; - for (TYPENAME known_map::iterator node = i; - node->second.valid(); - node = known.find(arc_from(node->second))) - this_path.insert(this_path.begin(),node->second); - result.push_back(this_path); - } - } - return result; - } - - template - TYPENAME digraph::path_vector - digraph::shortest_paths(TYPENAME digraph::iterator from, - TYPENAME digraph::arc_select_fn select) - throw(wrong_object,null_dereference,end_dereference) - { - return deconstify_paths(shortest_paths(from.constify(),select)); - } - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Note: I tried to write this using STL lists for the node and arc lists, but +// it got far too hairy. The specific problem is that I wanted a digraph +// iterator to contain a list::iterator so I needed to be able to generate a +// list::iterator from a node or arc and STL list iterators don't give you that +// functionality. I tried burgling the data structures, but that was +// non-portable between different STL implementations so needed lots of #ifdefs +// and so was mind-bogglingly awful and unreadable - in other words a +// maintenance nightmare. I gave up and impemented my own lists - not difficult. + +// I use circular double-linked lists. The circular design means that both +// ends of the list are equally accessible in unit time. An empty list +// contains no objects. There is no end node in the list - unlike the STL +// lists which have a dummy node for end iterators to point to - +// conceptually the end iterator points one element beyond the end of the +// list. However, I implement the end iterator concept in the iterator +// itself, so do not need the dummy end node. + +//////////////////////////////////////////////////////////////////////////////// +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +// Internals + +namespace stlplus +{ + + template + class digraph_node + { + public: + master_iterator, digraph_node > m_master; + NT m_data; + digraph_node* m_prev; + digraph_node* m_next; + std::vector*> m_inputs; + std::vector*> m_outputs; + public: + digraph_node(const digraph* owner, const NT& d = NT()) : + m_master(owner,this), m_data(d), m_prev(0), m_next(0) + { + } + ~digraph_node(void) + { + } + }; + + template + class digraph_arc + { + public: + master_iterator, digraph_arc > m_master; + AT m_data; + digraph_arc* m_prev; + digraph_arc* m_next; + digraph_node* m_from; + digraph_node* m_to; + digraph_arc(const digraph* owner, digraph_node* from = 0, digraph_node* to = 0, const AT& d = AT()) : + m_master(owner,this), m_data(d), m_prev(0), m_next(0), m_from(from), m_to(to) + { + } + }; + + //////////////////////////////////////////////////////////////////////////////// + // Iterators + //////////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////////// + // Node iterator + + // construct a null iterator + template + digraph_iterator::digraph_iterator(void) + { + } + + // valid iterator + template + digraph_iterator::digraph_iterator(digraph_node* node) : + safe_iterator,digraph_node >(node->m_master) + { + } + + // end iterator + template + digraph_iterator::digraph_iterator(const digraph* owner) : + safe_iterator,digraph_node >(owner) + { + } + + // alias an iterator + template + digraph_iterator::digraph_iterator(const safe_iterator, digraph_node >& iterator) : + safe_iterator,digraph_node >(iterator) + { + } + + // destructor + template + digraph_iterator::~digraph_iterator(void) + { + } + + template + TYPENAME digraph_iterator::const_iterator digraph_iterator::constify (void) const + { + return digraph_iterator(*this); + } + + template + TYPENAME digraph_iterator::iterator digraph_iterator::deconstify (void) const + { + return digraph_iterator(*this); + } + + template + TYPENAME digraph_iterator::this_iterator& digraph_iterator::operator ++ (void) + throw(null_dereference,end_dereference) + { + this->assert_valid(); + if (this->node()->m_next) + this->set(this->node()->m_next->m_master); + else + this->set_end(); + return *this; + } + + template + TYPENAME digraph_iterator::this_iterator digraph_iterator::operator ++ (int) + throw(null_dereference,end_dereference) + { + // post-increment is defined in terms of the pre-increment + digraph_iterator result(*this); + ++(*this); + return result; + } + + template + TYPENAME digraph_iterator::this_iterator& digraph_iterator::operator -- (void) + throw(null_dereference,end_dereference) + { + this->assert_valid(); + if (this->node()->m_prev) + this->set(this->node()->m_prev->m_master); + else + this->set_end(); + return *this; + } + + template + TYPENAME digraph_iterator::this_iterator digraph_iterator::operator -- (int) + throw(null_dereference,end_dereference) + { + // post-decrement is defined in terms of the pre-decrement + digraph_iterator result(*this); + --(*this); + return result; + } + + template + bool digraph_iterator::operator == (const TYPENAME digraph_iterator::this_iterator& r) const + { + return equal(r); + } + + template + bool digraph_iterator::operator != (const TYPENAME digraph_iterator::this_iterator& r) const + { + return !operator==(r); + } + + template + bool digraph_iterator::operator < (const TYPENAME digraph_iterator::this_iterator& r) const + { + return compare(r) < 0; + } + + template + TYPENAME digraph_iterator::reference digraph_iterator::operator*(void) const + throw(null_dereference,end_dereference) + { + this->assert_valid(); + return this->node()->m_data; + } + + template + TYPENAME digraph_iterator::pointer digraph_iterator::operator->(void) const + throw(null_dereference,end_dereference) + { + return &(operator*()); + } + + //////////////////////////////////////////////////////////////////////////////// + // Arc Iterator + + template + digraph_arc_iterator::digraph_arc_iterator(void) + { + } + + // valid iterator + template + digraph_arc_iterator::digraph_arc_iterator(digraph_arc* arc) : + safe_iterator,digraph_arc >(arc->m_master) + { + } + + // end iterator + template + digraph_arc_iterator::digraph_arc_iterator(const digraph* owner) : + safe_iterator,digraph_arc >(owner) + { + } + + // alias an iterator + template + digraph_arc_iterator::digraph_arc_iterator(const safe_iterator, digraph_arc >& iterator) : + safe_iterator,digraph_arc >(iterator) + { + } + + template + digraph_arc_iterator::~digraph_arc_iterator(void) + { + } + + template + TYPENAME digraph_arc_iterator::const_iterator digraph_arc_iterator::constify (void) const + { + return digraph_arc_iterator(*this); + } + + template + TYPENAME digraph_arc_iterator::iterator digraph_arc_iterator::deconstify (void) const + { + return digraph_arc_iterator(*this); + } + + template + TYPENAME digraph_arc_iterator::this_iterator& digraph_arc_iterator::operator ++ (void) + throw(null_dereference,end_dereference) + { + this->assert_valid(); + if (this->node()->m_next) + this->set(this->node()->m_next->m_master); + else + this->set_end(); + return *this; + } + + template + TYPENAME digraph_arc_iterator::this_iterator digraph_arc_iterator::operator ++ (int) + throw(null_dereference,end_dereference) + { + // post-increment is defined in terms of the pre-increment + digraph_arc_iterator result(*this); + ++(*this); + return result; + } + + template + TYPENAME digraph_arc_iterator::this_iterator& digraph_arc_iterator::operator -- (void) + throw(null_dereference,end_dereference) + { + this->assert_valid(); + if (this->node()->m_prev) + this->set(this->node()->m_prev->m_master); + else + this->set_end(); + return *this; + } + + template + TYPENAME digraph_arc_iterator::this_iterator digraph_arc_iterator::operator -- (int) + throw(null_dereference,end_dereference) + { + // post-decrement is defined in terms of the pre-decrement + digraph_arc_iterator result(*this); + --(*this); + return result; + } + + template + bool digraph_arc_iterator::operator == (const TYPENAME digraph_arc_iterator::this_iterator& r) const + { + return equal(r); + } + + template + bool digraph_arc_iterator::operator != (const TYPENAME digraph_arc_iterator::this_iterator& r) const + { + return !operator==(r); + } + + template + bool digraph_arc_iterator::operator < (const TYPENAME digraph_arc_iterator::this_iterator& r) const + { + return compare(r) < 0; + } + + template + TYPENAME digraph_arc_iterator::reference digraph_arc_iterator::operator*(void) const + throw(null_dereference,end_dereference) + { + this->assert_valid(); + return this->node()->m_data; + } + + template + TYPENAME digraph_arc_iterator::pointer digraph_arc_iterator::operator->(void) const + throw(null_dereference,end_dereference) + { + return &(operator*()); + } + + //////////////////////////////////////////////////////////////////////////////// + // subtype utilities + + template + TYPENAME digraph::const_arc_vector digraph::constify_arcs(const TYPENAME digraph::arc_vector& arcs) const + throw(wrong_object,null_dereference,end_dereference) + { + std::vector > result; + for (unsigned i = 0; i < arcs.size(); i++) + { + arcs[i].assert_valid(this); + result.push_back(arcs[i].constify()); + } + return result; + } + + template + TYPENAME digraph::arc_vector digraph::deconstify_arcs(const TYPENAME digraph::const_arc_vector& arcs) const + throw(wrong_object,null_dereference,end_dereference) + { + std::vector > result; + for (unsigned i = 0; i < arcs.size(); i++) + { + arcs[i].assert_valid(this); + result.push_back(arcs[i].deconstify()); + } + return result; + } + + template + TYPENAME digraph::const_path_vector digraph::constify_paths(const TYPENAME digraph::path_vector& paths) const + throw(wrong_object,null_dereference,end_dereference) + { + std::vector > > result; + for (unsigned i = 0; i < paths.size(); i++) + result.push_back(constify_arcs(paths[i])); + return result; + } + + template + TYPENAME digraph::path_vector digraph::deconstify_paths(const TYPENAME digraph::const_path_vector& paths) const + throw(wrong_object,null_dereference,end_dereference) + { + std::vector > > result; + for (unsigned i = 0; i < paths.size(); i++) + result.push_back(deconstify_arcs(paths[i])); + return result; + } + + template + TYPENAME digraph::const_node_vector digraph::constify_nodes(const TYPENAME digraph::node_vector& nodes) const + throw(wrong_object,null_dereference,end_dereference) + { + std::vector > result; + for (unsigned i = 0; i < nodes.size(); i++) + { + nodes[i].assert_valid(this); + result.push_back(nodes[i].constify()); + } + return result; + } + + template + TYPENAME digraph::node_vector digraph::deconstify_nodes(const TYPENAME digraph::const_node_vector& nodes) const + throw(wrong_object,null_dereference,end_dereference) + { + std::vector > result; + for (unsigned i = 0; i < nodes.size(); i++) + { + nodes[i].assert_valid(this); + result.push_back(nodes[i].deconstify()); + } + return result; + } + + template + unsigned digraph::npos(void) + { + return(unsigned)-1; + } + + //////////////////////////////////////////////////////////////////////////////// + // Constructors etc. + + template + digraph::digraph(void) : + m_nodes_begin(0), m_nodes_end(0), m_arcs_begin(0), m_arcs_end(0) + { + // node and arc lists are circular double-linked lists + // they start out empty (no dummy end node) + } + + template + digraph::~digraph(void) + { + clear(); + } + + template + digraph::digraph(const digraph& r) : + m_nodes_begin(0), m_nodes_end(0), m_arcs_begin(0), m_arcs_end(0) + { + *this = r; + } + + template + digraph& digraph::operator=(const digraph& r) + { + // make it self-copy safe i.e. a=a; is a valid instruction + if (this == &r) return *this; + clear(); + // first phase is to copy the nodes, creating a map of cross references from the old nodes to their new equivalents + std::map, digraph_iterator > xref; + for (digraph_iterator n = r.begin(); n != r.end(); n++) + xref[n] = insert(*n); + // second phase is to copy the arcs, using the map to convert the old to and from nodes to the new nodes + for (digraph_arc_iterator a = r.arc_begin(); a != r.arc_end(); a++) + arc_insert(xref[r.arc_from(a)],xref[r.arc_to(a)],*a); + return *this; + } + + //////////////////////////////////////////////////////////////////////////////// + // Basic Node functions + + template + bool digraph::empty(void) const + { + return m_nodes_begin == 0; + } + + template + unsigned digraph::size(void) const + { + unsigned count = 0; + for (digraph_iterator i = begin(); i != end(); i++) + count++; + return count; + } + + template + TYPENAME digraph::iterator digraph::insert(const NT& node_data) + { + digraph_node* new_node = new digraph_node(this,node_data); + if (!m_nodes_end) + { + // insert into an empty list + m_nodes_begin = new_node; + m_nodes_end = new_node; + } + else + { + // insert at the end of the list + new_node->m_prev = m_nodes_end; + m_nodes_end->m_next = new_node; + m_nodes_end = new_node; + } + return digraph_iterator(new_node); + } + + template + TYPENAME digraph::iterator digraph::erase(TYPENAME digraph::iterator iter) + throw(wrong_object,null_dereference,end_dereference) + { + iter.assert_valid(this); + // remove all arcs connected to this node first + // use arc_erase rather than arcs.erase because that tidies up the node at the other end of the arc too + for (unsigned i = fanin(iter); i--; ) + arc_erase(input(iter,i)); + for (unsigned j = fanout(iter); j--; ) + arc_erase(output(iter,j)); + // now unlink the node from the list and delete it + if (iter.node()->m_next) + iter.node()->m_next->m_prev = iter.node()->m_prev; + if (iter.node()->m_prev) + iter.node()->m_prev->m_next = iter.node()->m_next; + digraph_node* next = iter.node()->m_next; + delete iter.node(); + // return the next node in the list + if (next) + return digraph_iterator(next); + else + return digraph_iterator(this); + } + + template + void digraph::clear(void) + { + // clearing the nodes also clears the arcs + for (digraph_iterator i = begin(); i != end(); ) + i = erase(i); + } + + template + TYPENAME digraph::const_iterator digraph::begin(void) const + { + if (m_nodes_begin) + return digraph_iterator(m_nodes_begin); + else + return digraph_iterator(this); + } + + template + TYPENAME digraph::iterator digraph::begin(void) + { + if (m_nodes_begin) + return digraph_iterator(m_nodes_begin); + else + return digraph_iterator(this); + } + + template + TYPENAME digraph::const_iterator digraph::end(void) const + { + return digraph_iterator(this); + } + + template + TYPENAME digraph::iterator digraph::end(void) + { + return digraph_iterator(this); + } + + template + unsigned digraph::fanin(TYPENAME digraph::const_iterator iter) const + throw(wrong_object,null_dereference,end_dereference) + { + iter.assert_valid(this); + return iter.node()->m_inputs.size(); + } + + template + unsigned digraph::fanin(TYPENAME digraph::iterator iter) + throw(wrong_object,null_dereference,end_dereference) + { + iter.assert_valid(this); + return iter.node()->m_inputs.size(); + } + + template + TYPENAME digraph::const_arc_iterator digraph::input(TYPENAME digraph::const_iterator iter, unsigned i) const + throw(wrong_object,null_dereference,end_dereference,std::out_of_range) + { + iter.assert_valid(this); + if (i >= iter.node()->m_inputs.size()) throw std::out_of_range("digraph::input"); + return digraph_arc_iterator(iter.node()->m_inputs[i]); + } + + template + TYPENAME digraph::arc_iterator digraph::input(TYPENAME digraph::iterator iter, unsigned i) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range) + { + iter.assert_valid(this); + if (i >= iter.node()->m_inputs.size()) throw std::out_of_range("digraph::input"); + return digraph_arc_iterator(iter.node()->m_inputs[i]); + } + + template + unsigned digraph::fanout(TYPENAME digraph::const_iterator iter) const + throw(wrong_object,null_dereference,end_dereference) + { + iter.assert_valid(this); + return iter.node()->m_outputs.size(); + } + + template + unsigned digraph::fanout(TYPENAME digraph::iterator iter) + throw(wrong_object,null_dereference,end_dereference) + { + iter.assert_valid(this); + return iter.node()->m_outputs.size(); + } + + template + TYPENAME digraph::const_arc_iterator digraph::output(TYPENAME digraph::const_iterator iter, unsigned i) const + throw(wrong_object,null_dereference,end_dereference,std::out_of_range) + { + iter.assert_valid(this); + if (i >= iter.node()->m_outputs.size()) throw std::out_of_range("digraph::output"); + return digraph_arc_iterator(iter.node()->m_outputs[i]); + } + + template + TYPENAME digraph::arc_iterator digraph::output(TYPENAME digraph::iterator iter, unsigned i) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range) + { + iter.assert_valid(this); + if (i >= iter.node()->m_outputs.size()) throw std::out_of_range("digraph::output"); + return digraph_arc_iterator(iter.node()->m_outputs[i]); + } + + template + TYPENAME digraph::const_arc_vector digraph::inputs(TYPENAME digraph::const_iterator node) const + throw(wrong_object,null_dereference,end_dereference) + { + node.assert_valid(this); + std::vector > result; + for (unsigned i = 0; i < fanin(node); i++) + result.push_back(input(node,i)); + return result; + } + + template + TYPENAME digraph::arc_vector digraph::inputs(TYPENAME digraph::iterator node) + throw(wrong_object,null_dereference,end_dereference) + { + node.assert_valid(this); + std::vector > result; + for (unsigned i = 0; i < fanin(node); i++) + result.push_back(input(node,i)); + return result; + } + + template + TYPENAME digraph::const_arc_vector digraph::outputs(TYPENAME digraph::const_iterator node) const + throw(wrong_object,null_dereference,end_dereference) + { + node.assert_valid(this); + std::vector > result; + for (unsigned i = 0; i < fanout(node); i++) + result.push_back(output(node,i)); + return result; + } + + template + TYPENAME digraph::arc_vector digraph::outputs(TYPENAME digraph::iterator node) + throw(wrong_object,null_dereference,end_dereference) + { + node.assert_valid(this); + std::vector > result; + for (unsigned i = 0; i < fanout(node); i++) + result.push_back(output(node,i)); + return result; + } + + template + unsigned digraph::output_offset(TYPENAME digraph::const_iterator from, + TYPENAME digraph::const_arc_iterator arc) const + throw(wrong_object,null_dereference,end_dereference) + { + from.assert_valid(this); + arc.assert_valid(this); + for (unsigned i = 0; i < fanout(from); i++) + { + if (output(from,i) == arc) + return i; + } + return digraph::npos(); + } + + template + unsigned digraph::output_offset(TYPENAME digraph::iterator from, + TYPENAME digraph::arc_iterator arc) + throw(wrong_object,null_dereference,end_dereference) + { + from.assert_valid(this); + arc.assert_valid(this); + for (unsigned i = 0; i < fanout(from); i++) + { + if (output(from,i) == arc) + return i; + } + return digraph::npos(); + } + + template + unsigned digraph::input_offset(TYPENAME digraph::const_iterator to, + TYPENAME digraph::const_arc_iterator arc) const + throw(wrong_object,null_dereference,end_dereference) + { + to.assert_valid(this); + arc.assert_valid(this); + for (unsigned i = 0; i < fanin(to); i++) + { + if (input(to,i) == arc) + return i; + } + return digraph::npos(); + } + + template + unsigned digraph::input_offset(TYPENAME digraph::iterator to, + TYPENAME digraph::arc_iterator arc) + throw(wrong_object,null_dereference,end_dereference) + { + to.assert_valid(this); + arc.assert_valid(this); + for (unsigned i = 0; i < fanin(to); i++) + { + if (input(to,i) == arc) + return i; + } + return digraph::npos(); + } + + //////////////////////////////////////////////////////////////////////////////// + // Basic Arc functions + + template + bool digraph::arc_empty(void) const + { + return m_arcs_end == 0; + } + + template + unsigned digraph::arc_size(void) const + { + unsigned count = 0; + for (digraph_arc_iterator i = arc_begin(); i != arc_end(); i++) + count++; + return count; + } + + template + TYPENAME digraph::arc_iterator digraph::arc_insert(TYPENAME digraph::iterator from, + TYPENAME digraph::iterator to, + const AT& arc_data) + throw(wrong_object,null_dereference,end_dereference) + { + from.assert_valid(this); + to.assert_valid(this); + // create the new arc and link it in to the arc list + digraph_arc* new_arc = new digraph_arc(this, from.node(), to.node(), arc_data); + if (!m_arcs_end) + { + // insert into an empty list + m_arcs_begin = new_arc; + m_arcs_end = new_arc; + } + else + { + // insert at the end of the list + new_arc->m_prev = m_arcs_end; + m_arcs_end->m_next = new_arc; + m_arcs_end = new_arc; + } + // add this arc to the inputs and outputs of the end nodes + from.node()->m_outputs.push_back(new_arc); + to.node()->m_inputs.push_back(new_arc); + return digraph_arc_iterator(new_arc); + } + + template + TYPENAME digraph::arc_iterator digraph::arc_erase(TYPENAME digraph::arc_iterator iter) + throw(wrong_object,null_dereference,end_dereference) + { + iter.assert_valid(this); + // first remove this arc's pointers from the from/to nodes + for (TYPENAME std::vector*>::iterator i = iter.node()->m_to->m_inputs.begin(); i != iter.node()->m_to->m_inputs.end(); ) + { + if (*i == iter.node()) + i = iter.node()->m_to->m_inputs.erase(i); + else + i++; + } + for (TYPENAME std::vector*>::iterator o = iter.node()->m_from->m_outputs.begin(); o != iter.node()->m_from->m_outputs.end(); ) + { + if (*o == iter.node()) + o = iter.node()->m_from->m_outputs.erase(o); + else + o++; + } + // now unlink the arc from the list and delete it + if (iter.node()->m_next) + iter.node()->m_next->m_prev = iter.node()->m_prev; + if (iter.node()->m_prev) + iter.node()->m_prev->m_next = iter.node()->m_next; + digraph_arc* next = iter.node()->m_next; + delete iter.node(); + if (next) + return digraph_arc_iterator(next); + else + return digraph_arc_iterator(this); + } + + template + void digraph::arc_clear(void) + { + for (digraph_arc_iterator a = arc_begin(); a != arc_end(); ) + a = arc_erase(a); + } + + template + TYPENAME digraph::const_arc_iterator digraph::arc_begin(void) const + { + if (m_arcs_begin) + return digraph_arc_iterator(m_arcs_begin); + else + return digraph_arc_iterator(this); + } + + template + TYPENAME digraph::arc_iterator digraph::arc_begin(void) + { + if (m_arcs_begin) + return digraph_arc_iterator(m_arcs_begin); + else + return digraph_arc_iterator(this); + } + + template + TYPENAME digraph::const_arc_iterator digraph::arc_end(void) const + { + return digraph_arc_iterator(this); + } + + template + TYPENAME digraph::arc_iterator digraph::arc_end(void) + { + return digraph_arc_iterator(this); + } + + template + TYPENAME digraph::const_iterator digraph::arc_from(TYPENAME digraph::const_arc_iterator iter) const + throw(wrong_object,null_dereference,end_dereference) + { + iter.assert_valid(this); + return digraph_iterator(iter.node()->m_from); + } + + template + TYPENAME digraph::iterator digraph::arc_from(TYPENAME digraph::arc_iterator iter) + throw(wrong_object,null_dereference,end_dereference) + { + iter.assert_valid(this); + return digraph_iterator(iter.node()->m_from); + } + + template + TYPENAME digraph::const_iterator digraph::arc_to(TYPENAME digraph::const_arc_iterator iter) const + throw(wrong_object,null_dereference,end_dereference) + { + iter.assert_valid(this); + return digraph_iterator(iter.node()->m_to); + } + + template + TYPENAME digraph::iterator digraph::arc_to(TYPENAME digraph::arc_iterator iter) + throw(wrong_object,null_dereference,end_dereference) + { + iter.assert_valid(this); + return digraph_iterator(iter.node()->m_to); + } + + template + void digraph::arc_move(TYPENAME digraph::arc_iterator arc, + TYPENAME digraph::iterator from, + TYPENAME digraph::iterator to) + throw(wrong_object,null_dereference,end_dereference) + { + arc_move_to(arc,to); + arc_move_from(arc,from); + } + + template + void digraph::arc_move_from(TYPENAME digraph::arc_iterator arc, + TYPENAME digraph::iterator from) + throw(wrong_object,null_dereference,end_dereference) + { + arc.assert_valid(this); + from.assert_valid(this); + for (TYPENAME std::vector*>::iterator o = arc.node()->m_from->m_outputs.begin(); o != arc.node()->m_from->m_outputs.end(); ) + { + if (*o == arc.node()) + o = arc.node()->m_from->m_outputs.erase(o); + else + o++; + } + from.node()->m_outputs.push_back(arc.node()); + arc.node()->m_from = from.node(); + } + + template + void digraph::arc_move_to(TYPENAME digraph::arc_iterator arc, + TYPENAME digraph::iterator to) + throw(wrong_object,null_dereference,end_dereference) + { + arc.assert_valid(this); + to.assert_valid(this); + for (TYPENAME std::vector*>::iterator i = arc.node()->m_to->m_inputs.begin(); i != arc.node()->m_to->m_inputs.end(); ) + { + if (*i == arc.node()) + i = arc.node()->m_to->m_inputs.erase(i); + else + i++; + } + to.node()->m_inputs.push_back(arc.node()); + arc.node()->m_to = to.node(); + } + + template + void digraph::arc_flip(TYPENAME digraph::arc_iterator arc) + throw(wrong_object,null_dereference,end_dereference) + { + arc_move(arc,arc_to(arc),arc_from(arc)); + } + + //////////////////////////////////////////////////////////////////////////////// + // Adjacency Algorithms + + template + bool digraph::adjacent(TYPENAME digraph::const_iterator from, + TYPENAME digraph::const_iterator to) const + throw(wrong_object,null_dereference,end_dereference) + { + return adjacent_arc(from,to) != arc_end(); + } + + template + bool digraph::adjacent(TYPENAME digraph::iterator from, + TYPENAME digraph::iterator to) + throw(wrong_object,null_dereference,end_dereference) + { + return adjacent_arc(from,to) != arc_end(); + } + + template + TYPENAME digraph::const_arc_iterator digraph::adjacent_arc(TYPENAME digraph::const_iterator from, + TYPENAME digraph::const_iterator to) const + throw(wrong_object,null_dereference,end_dereference) + { + from.assert_valid(this); + to.assert_valid(this); + for (unsigned arc = 0; arc < fanout(from); arc++) + { + if (arc_to(output(from, arc)) == to) + return output(from,arc); + } + return arc_end(); + } + + template + TYPENAME digraph::arc_iterator digraph::adjacent_arc(TYPENAME digraph::iterator from, + TYPENAME digraph::iterator to) + throw(wrong_object,null_dereference,end_dereference) + { + return adjacent_arc(from.constify(), to.constify()).deconstify(); + } + + template + TYPENAME digraph::const_arc_vector digraph::adjacent_arcs(TYPENAME digraph::const_iterator from, + TYPENAME digraph::const_iterator to) const + throw(wrong_object,null_dereference,end_dereference) + { + from.assert_valid(this); + to.assert_valid(this); + std::vector > result; + for (unsigned arc = 0; arc < fanout(from); arc++) + { + if (arc_to(output(from, arc)) == to) + result.push_back(output(from,arc)); + } + return result; + } + + template + TYPENAME digraph::arc_vector digraph::adjacent_arcs(TYPENAME digraph::iterator from, + TYPENAME digraph::iterator to) + throw(wrong_object,null_dereference,end_dereference) + { + return deconstify_arcs(adjacent_arcs(from.constify(), to.constify())); + } + + template + TYPENAME digraph::const_node_vector digraph::input_adjacencies(TYPENAME digraph::const_iterator to) const + throw(wrong_object,null_dereference,end_dereference) + { + std::vector > result; + for (unsigned arc = 0; arc < fanin(to); arc++) + { + digraph_iterator from = arc_from(input(to, arc)); + if (std::find(result.begin(), result.end(), from) == result.end()) + result.push_back(from); + } + return result; + } + + template + TYPENAME digraph::node_vector digraph::input_adjacencies(TYPENAME digraph::iterator to) + throw(wrong_object,null_dereference,end_dereference) + { + return deconstify_nodes(input_adjacencies(to.constify())); + } + + template + TYPENAME digraph::const_node_vector digraph::output_adjacencies(TYPENAME digraph::const_iterator from) const + throw(wrong_object,null_dereference,end_dereference) + { + std::vector > result; + for (unsigned arc = 0; arc < fanout(from); arc++) + { + digraph_iterator to = arc_to(output(from, arc)); + if (find(result.begin(), result.end(), to) == result.end()) + result.push_back(to); + } + return result; + } + + template + TYPENAME digraph::node_vector digraph::output_adjacencies(TYPENAME digraph::iterator from) + throw(wrong_object,null_dereference,end_dereference) + { + return deconstify_nodes(output_adjacencies(from.constify())); + } + + //////////////////////////////////////////////////////////////////////////////// + // Topographical Sort Algorithms + + template + std::pair::const_node_vector, TYPENAME digraph::const_arc_vector> + digraph::sort(TYPENAME digraph::arc_select_fn select) const + { + std::vector > result; + std::vector > errors; + // build a map containing the number of fanins to each node that must be visited before this one + std::map,unsigned> fanin_map; + for (digraph_iterator n = begin(); n != end(); n++) + { + unsigned predecessors = 0; + // only count predecessors connected by selected arcs + for (unsigned f = 0; f < fanin(n); f++) + { + digraph_arc_iterator input_arc = input(n,f); + digraph_iterator predecessor = arc_from(input_arc); + if (!select || select(*this,input_arc)) + predecessors++; + } + if (predecessors == 0) + { + result.push_back(n); + } + else + { + fanin_map[n] = predecessors; + } + } + // main algorithm applies the topographical sort repeatedly. For a DAG, it + // will complete first time. However, with backward arcs, the first + // iteration will fail. The algorithm then tries breaking random arcs to try + // to get an ordering. + for(unsigned i = 0; !fanin_map.empty(); ) + { + // now visit each node in traversal order, decrementing the fanin count of + // all successors. As each successor's fanin count goes to zero, it is + // appended to the result. + for (; i < result.size(); i++) + { + // Note: dereferencing gives us a node iterator + digraph_iterator current = result[i]; + for (unsigned f = 0; f < fanout(current); f++) + { + // only consider successors connected by selected arcs + digraph_arc_iterator output_arc = output(current, f); + digraph_iterator successor = arc_to(output_arc); + if (!select || select(*this,output_arc)) + { + // don't consider arcs that have been eliminated to break a loop + if (fanin_map.find(successor) != fanin_map.end()) + { + --fanin_map[successor]; + if ((fanin_map[successor]) == 0) + { + result.push_back(successor); + fanin_map.erase(fanin_map.find(successor)); + } + } + } + } + } + if (!fanin_map.empty()) + { + // there must be backward arcs preventing completion + // try removing arcs from the sort to get a partial ordering containing all the nodes + + // select an arc that is still relevant to the sort and break it + // first select a node that has non-zero fanin and its predecessor that has non-zero fanin + digraph_iterator stuck_node = fanin_map.begin()->first; + for (unsigned f = 0; f < fanin(stuck_node); f++) + { + // now successively remove input arcs that are still part of the sort until the fanin reduces to zero + // first find a relevant arc - this must be a selected arc that has not yet been traversed by the first half of the algorithm + digraph_arc_iterator input_arc = input(stuck_node, f); + if (!select || select(*this,input_arc)) + { + digraph_iterator predecessor = arc_from(input_arc); + if (fanin_map.find(predecessor) != fanin_map.end()) + { + // found the right combination - remove this arc and then drop out of the fanin loop to restart the outer sort loop + errors.push_back(input_arc); + --fanin_map[stuck_node]; + if ((fanin_map[stuck_node]) == 0) + { + result.push_back(stuck_node); + fanin_map.erase(fanin_map.find(stuck_node)); + break; + } + } + } + } + } + } + return std::make_pair(result,errors); + } + + template + std::pair::node_vector, TYPENAME digraph::arc_vector> + digraph::sort(TYPENAME digraph::arc_select_fn select) + { + std::pair >, + std::vector > > const_result = + const_cast*>(this)->sort(select); + + std::pair >, + std::vector > > result = + std::make_pair(deconstify_nodes(const_result.first),deconstify_arcs(const_result.second)); + return result; + } + + template + TYPENAME digraph::const_node_vector digraph::dag_sort(TYPENAME digraph::arc_select_fn select) const + { + std::pair >, + std::vector > > result = sort(select); + if (result.second.empty()) return result.first; + return std::vector >(); + } + + template + TYPENAME digraph::node_vector digraph::dag_sort(TYPENAME digraph::arc_select_fn select) + { + return deconstify_nodes(const_cast*>(this)->dag_sort(select)); + } + //////////////////////////////////////////////////////////////////////////////// + // Path Algorithms + + template + bool digraph::path_exists_r(TYPENAME digraph::const_iterator from, + TYPENAME digraph::const_iterator to, + TYPENAME digraph::const_iterator_set& visited, + TYPENAME digraph::arc_select_fn select) const + throw(wrong_object,null_dereference,end_dereference) + { + // Recursive part of the digraph::path_exists function. This is based on a + // depth first search algorithm and stops the moment it finds a path + // regardless of its length. Simply traverse every output and recurse on that + // node until we find the to node or run out of things to recurse on. However, + // to avoid infinite recursion due to cycles in the graph, I need to maintain + // a set of visited nodes. The visited set is updated when a candidate is + // found but tested before the recursion on the candidate so that the number of + // function calls is minimised. + for (unsigned i = 0; i < fanout(from); i++) + { + digraph_arc_iterator arc = output(from,i); + if (!select || select(*this, arc)) + { + digraph_iterator node = arc_to(arc); + // if the node is the target, return immediately + if (node == to) return true; + // update the visited set and give up if the insert fails, which indicates that the node has already been visited + if (!(visited.insert(node).second)) return false; + // now recurse - a path exists from from to to if a path exists from an adjacent node to to + if (path_exists_r(node,to,visited,select)) return true; + } + } + return false; + } + + template + bool digraph::path_exists(TYPENAME digraph::const_iterator from, + TYPENAME digraph::const_iterator to, + TYPENAME digraph::arc_select_fn select) const + throw(wrong_object,null_dereference,end_dereference) + { + // set up the recursion with its initial visited set and then recurse + std::set > visited; + visited.insert(from); + return path_exists_r(from, to, visited, select); + } + + template + bool digraph::path_exists(TYPENAME digraph::iterator from, + TYPENAME digraph::iterator to, + TYPENAME digraph::arc_select_fn select) + throw(wrong_object,null_dereference,end_dereference) + { + return path_exists(from.constify(), to.constify(), select); + } + + template + void digraph::all_paths_r(TYPENAME digraph::const_iterator from, + TYPENAME digraph::const_iterator to, + TYPENAME digraph::const_arc_vector& so_far, + TYPENAME digraph::const_path_vector& result, + TYPENAME digraph::arc_select_fn select) const + throw(wrong_object,null_dereference,end_dereference) + { + // This is the recursive part of the all_paths function. The field so_far + // contains the path so far so that when 'to' is reached, the path is + // complete. It serves the same purpose as the visited set in the path_exists + // function except that it also preserves the path order. It also serves the + // purpose of detecting cycles and thus stopping infinite recursion. Every + // time the recursion reaches the to node, a copy of so_far is appended to the + // path set. + for (unsigned i = 0; i < fanout(from); i++) + { + digraph_arc_iterator candidate = output(from,i); + // assert_valid that the arc is selected and then assert_valid that the candidate has not + // been visited on this path and only allow further recursion if it hasn't + if ((!select || select(*this, candidate)) && std::find(so_far.begin(), so_far.end(), candidate) == so_far.end()) + { + // extend the path tracing the route to this arc + so_far.push_back(candidate); + // if the candidate arc points to the target, update the result set and prevent further recursion, otherwise recurse + if (arc_to(candidate) == to) + result.push_back(so_far); + else + all_paths_r(arc_to(candidate),to,so_far,result,select); + so_far.pop_back(); + } + } + } + + template + TYPENAME digraph::const_path_vector + digraph::all_paths(TYPENAME digraph::const_iterator from, + TYPENAME digraph::const_iterator to, + TYPENAME digraph::arc_select_fn select) const + throw(wrong_object,null_dereference,end_dereference) + { + // set up the recursion with empty data fields and then recurse + std::vector > > result; + std::vector > so_far; + all_paths_r(from, to, so_far, result, select); + return result; + } + + template + TYPENAME digraph::path_vector + digraph::all_paths(TYPENAME digraph::iterator from, + TYPENAME digraph::iterator to, + TYPENAME digraph::arc_select_fn select) + throw(wrong_object,null_dereference,end_dereference) + { + return deconstify_paths(all_paths(from.constify(), to.constify(), select)); + } + + template + void digraph::reachable_nodes_r(TYPENAME digraph::const_iterator from, + TYPENAME digraph::const_iterator_set& visited, + TYPENAME digraph::arc_select_fn select) const + throw(wrong_object,null_dereference,end_dereference) + { + // The recursive part of the reachable_nodes function. + // This is a depth-first traversal again but this time it carries on to find all the reachable nodes + // Just keep recursing on all the adjacent nodes of each node, skipping already visited nodes to avoid cycles + for (unsigned i = 0; i < fanout(from); i++) + { + digraph_arc_iterator arc = output(from,i); + if (!select || select(*this,arc)) + { + digraph_iterator candidate = arc_to(arc); + if (visited.insert(candidate).second) + reachable_nodes_r(candidate,visited,select); + } + } + } + + template + TYPENAME digraph::const_node_vector + digraph::reachable_nodes(TYPENAME digraph::const_iterator from, + TYPENAME digraph::arc_select_fn select) const + throw(wrong_object,null_dereference,end_dereference) + { + // seed the recursion, marking the starting node as already visited + std::set > visited; + visited.insert(from); + reachable_nodes_r(from, visited, select); + // convert the visited set into the required output form + // exclude the starting node + std::vector > result; + for (TYPENAME std::set >::iterator i = visited.begin(); i != visited.end(); i++) + if (*i != from) + result.push_back(*i); + return result; + } + + template + TYPENAME digraph::node_vector + digraph::reachable_nodes(TYPENAME digraph::iterator from, + TYPENAME digraph::arc_select_fn select) + throw(wrong_object,null_dereference,end_dereference) + { + return deconstify_nodes(reachable_nodes(from.constify(), select)); + } + + template + void digraph::reaching_nodes_r(TYPENAME digraph::const_iterator to, + TYPENAME digraph::const_iterator_set& visited, + TYPENAME digraph::arc_select_fn select) const + throw(wrong_object,null_dereference,end_dereference) + { + // The recursive part of the reaching_nodes function. + // Just like the reachable_nodes_r function but it goes backwards + for (unsigned i = 0; i < fanin(to); i++) + { + digraph_arc_iterator arc = input(to,i); + if (!select || select(*this,arc)) + { + digraph_iterator candidate = arc_from(input(to,i)); + if (visited.insert(candidate).second) + reaching_nodes_r(candidate,visited,select); + } + } + } + + template + TYPENAME digraph::const_node_vector + digraph::reaching_nodes(TYPENAME digraph::const_iterator to, + TYPENAME digraph::arc_select_fn select) const + throw(wrong_object,null_dereference,end_dereference) + { + // seed the recursion, marking the starting node as already visited + std::set > visited; + visited.insert(to); + reaching_nodes_r(to,visited,select); + // convert the visited set into the required output form + // exclude the end node + std::vector > result; + for (TYPENAME std::set >::iterator i = visited.begin(); i != visited.end(); i++) + if (*i != to) + result.push_back(*i); + return result; + } + + template + TYPENAME digraph::node_vector + digraph::reaching_nodes(TYPENAME digraph::iterator to, + TYPENAME digraph::arc_select_fn select) + throw(wrong_object,null_dereference,end_dereference) + { + return deconstify_nodes(reaching_nodes(to.constify(),select)); + } + + //////////////////////////////////////////////////////////////////////////////// + // Shortest Path Algorithms + + template + TYPENAME digraph::const_arc_vector + digraph::shortest_path(TYPENAME digraph::const_iterator from, + TYPENAME digraph::const_iterator to, + TYPENAME digraph::arc_select_fn select) const + throw(wrong_object,null_dereference,end_dereference) + { + std::vector > > paths = all_paths(from,to,select); + std::vector > shortest; + for (TYPENAME std::vector > >::iterator i = paths.begin(); i != paths.end(); i++) + if (shortest.empty() || i->size() < shortest.size()) + shortest = *i; + return shortest; + } + + template + TYPENAME digraph::arc_vector + digraph::shortest_path(TYPENAME digraph::iterator from, + TYPENAME digraph::iterator to, + TYPENAME digraph::arc_select_fn select) + throw(wrong_object,null_dereference,end_dereference) + { + return deconstify_arcs(shortest_path(from.constify(),to.constify(),select)); + } + + template + TYPENAME digraph::const_path_vector + digraph::shortest_paths(TYPENAME digraph::const_iterator from, + TYPENAME digraph::arc_select_fn select) const + throw(wrong_object,null_dereference,end_dereference) + { + from.assert_valid(this); + // This is an unweighted shortest path algorithm based on the algorithm from + // Weiss's book. This is essentially a breadth-first traversal or graph + // colouring algorithm. It is an iterative algorithm, so no recursion here! It + // works by creating a node queue initialised with the starting node. It then + // consumes the queue from front to back. For each node, it finds the + // successors and appends them to the queue. If a node is already 'known' it + // is not added - this avoids cycles. Thus the queue insert ordering + // represents the breadth-first ordering. On the way it creates a map of + // visited nodes. This is a map not a set because it also stores the arc that + // nominated this node as a shortest path. The full path can then be recreated + // from the map by just walking back through the predecessors. The depth (or + // colour) can be determined by the path length. + std::vector > > result; + // initialise the iteration by creating a queue and adding the start node + std::deque > nodes; + nodes.push_back(from); + // Create a map to store the set of known nodes mapped to their predecessor + // arcs. Initialise it with the current node, which has no predecessor. Note + // that the algorithm uses the feature of digraph iterators that they can be + // null iterators and that all null iterators are equal. + typedef std::map, + digraph_arc_iterator > known_map; + known_map known; + known.insert(std::make_pair(from,digraph_arc_iterator())); + // now the iterative part of the algorithm + while(!nodes.empty()) + { + // pop the queue to get the next node to process - unfortunately the STL + // deque::pop does not return the popped value + digraph_iterator current = nodes.front(); + nodes.pop_front(); + // now visit all the successors + for (unsigned i = 0; i < fanout(current); i++) + { + digraph_arc_iterator next_arc = output(current,i); + // assert_valid whether the successor arc is a selected arc and can be part of a path + if (!select || select(*this,next_arc)) + { + digraph_iterator next = arc_to(next_arc); + // Discard any successors that are known because to be known already they + // must have another shorter path. Otherwise add the successor node to the + // queue to be visited later. To minimise the overhead of map lookup I use + // the usual trick of trying to insert the node and determining whether + // the node was known by the success or failure of the insertion - this is + // a Good STL Trick (TM). + if (known.insert(std::make_pair(next,next_arc)).second) + nodes.push_back(next); + } + } + } + // The map contains the results as an unordered set of nodes, mapped to their + // predecessor arcs and weight. This now needs to be converted into a set of + // paths. This is done by starting with a node from the map, finding its + // predecessor arc and therefore its predecessor node, looking that up in the + // map to find its predecessor and so on until the start node is reached (it + // has a null predecessor). Note that the known set includes the from node + // which does not generate a path. + for (TYPENAME known_map::iterator i = known.begin(); i != known.end(); i++) + { + if (i->first != from) + { + const_arc_vector this_path; + for (TYPENAME known_map::iterator node = i; + node->second.valid(); + node = known.find(arc_from(node->second))) + this_path.insert(this_path.begin(),node->second); + result.push_back(this_path); + } + } + return result; + } + + template + TYPENAME digraph::path_vector + digraph::shortest_paths(TYPENAME digraph::iterator from, + TYPENAME digraph::arc_select_fn select) + throw(wrong_object,null_dereference,end_dereference) + { + return deconstify_paths(shortest_paths(from.constify(),select)); + } + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus diff --git a/src/stlplus/containers/exceptions.hpp b/src/stlplus/containers/exceptions.hpp index 1911013..f1aa141 100644 --- a/src/stlplus/containers/exceptions.hpp +++ b/src/stlplus/containers/exceptions.hpp @@ -1,71 +1,71 @@ -#ifndef STLPLUS_EXCEPTIONS -#define STLPLUS_EXCEPTIONS -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// The set of general exceptions thrown by STLplus components - -//////////////////////////////////////////////////////////////////////////////// -#include "containers_fixes.hpp" -#include -#include - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // Thrown if a pointer or an iterator is dereferenced when it is null - - class null_dereference : public std::logic_error - { - public: - null_dereference(const std::string& description) throw() : - std::logic_error(std::string("stlplus::null_dereference: ") + description) {} - ~null_dereference(void) throw() {} - }; - - //////////////////////////////////////////////////////////////////////////////// - // Thrown if an iterator is dereferenced when it is pointing to the end element - - class end_dereference : public std::logic_error - { - public: - end_dereference(const std::string& description) throw() : - std::logic_error("stlplus::end_dereference: " + description) {} - ~end_dereference(void) throw() {} - }; - - //////////////////////////////////////////////////////////////////////////////// - // Thrown if an iterator is used with the wrong container. In other words, an - // iterator is created as a pointer to a sub-object within a container. If - // that iterator is then used with a different container, this exception is - // thrown. - - class wrong_object : public std::logic_error - { - public: - wrong_object(const std::string& description) throw() : - std::logic_error("stlplus::wrong_object: " + description) {} - ~wrong_object(void) throw() {} - }; - - //////////////////////////////////////////////////////////////////////////////// - // Thrown if an attempt is made to copy an object that is uncopyable - - class illegal_copy : public std::logic_error - { - public: - illegal_copy(const std::string& description) throw() : - std::logic_error("stlplus::illegal_copy: " + description) {} - ~illegal_copy(void) throw() {} - }; - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#endif +#ifndef STLPLUS_EXCEPTIONS +#define STLPLUS_EXCEPTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// The set of general exceptions thrown by STLplus components + +//////////////////////////////////////////////////////////////////////////////// +#include "containers_fixes.hpp" +#include +#include + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // Thrown if a pointer or an iterator is dereferenced when it is null + + class null_dereference : public std::logic_error + { + public: + null_dereference(const std::string& description) throw() : + std::logic_error(std::string("stlplus::null_dereference: ") + description) {} + ~null_dereference(void) throw() {} + }; + + //////////////////////////////////////////////////////////////////////////////// + // Thrown if an iterator is dereferenced when it is pointing to the end element + + class end_dereference : public std::logic_error + { + public: + end_dereference(const std::string& description) throw() : + std::logic_error("stlplus::end_dereference: " + description) {} + ~end_dereference(void) throw() {} + }; + + //////////////////////////////////////////////////////////////////////////////// + // Thrown if an iterator is used with the wrong container. In other words, an + // iterator is created as a pointer to a sub-object within a container. If + // that iterator is then used with a different container, this exception is + // thrown. + + class wrong_object : public std::logic_error + { + public: + wrong_object(const std::string& description) throw() : + std::logic_error("stlplus::wrong_object: " + description) {} + ~wrong_object(void) throw() {} + }; + + //////////////////////////////////////////////////////////////////////////////// + // Thrown if an attempt is made to copy an object that is uncopyable + + class illegal_copy : public std::logic_error + { + public: + illegal_copy(const std::string& description) throw() : + std::logic_error("stlplus::illegal_copy: " + description) {} + ~illegal_copy(void) throw() {} + }; + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#endif diff --git a/src/stlplus/containers/foursome.hpp b/src/stlplus/containers/foursome.hpp index 46dc46d..adae146 100644 --- a/src/stlplus/containers/foursome.hpp +++ b/src/stlplus/containers/foursome.hpp @@ -1,59 +1,61 @@ -#ifndef STLPLUS_FOURSOME -#define STLPLUS_FOURSOME -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton, from an original by Dan Milton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// The next in the series pair->triple->foursome - -// Originally called quadruple but that clashed (as did quad) with system -// libraries on some operating systems - -//////////////////////////////////////////////////////////////////////////////// -#include "containers_fixes.hpp" - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // the foursome class - - template - struct foursome - { - typedef T1 first_type; - typedef T2 second_type; - typedef T3 third_type; - typedef T4 fourth_type; - - T1 first; - T2 second; - T3 third; - T4 fourth; - - foursome(void); - foursome(const T1& p1, const T2& p2, const T3& p3, const T4& p4); - foursome(const foursome& t2); - }; - - //////////////////////////////////////////////////////////////////////////////// - // creation - - template - foursome make_foursome(const T1& first, const T2& second, const T3& third, const T4& fourth); - - //////////////////////////////////////////////////////////////////////////////// - // comparison - - template - bool operator == (const foursome& left, const foursome& right); - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#include "foursome.tpp" -#endif +#ifndef STLPLUS_FOURSOME +#define STLPLUS_FOURSOME +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton, from an original by Dan Milton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// The next in the series pair->triple->foursome + +// Originally called quadruple but that clashed (as did quad) with system +// libraries on some operating systems + +//////////////////////////////////////////////////////////////////////////////// +#include "containers_fixes.hpp" + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // the foursome class + + template + struct foursome + { + typedef T1 first_type; + typedef T2 second_type; + typedef T3 third_type; + typedef T4 fourth_type; + + T1 first; + T2 second; + T3 third; + T4 fourth; + + foursome(void); + foursome(const T1& p1, const T2& p2, const T3& p3, const T4& p4); + foursome(const foursome& t2); + }; + + //////////////////////////////////////////////////////////////////////////////// + // creation + + template + foursome make_foursome(const T1& first, const T2& second, const T3& third, const T4& fourth); + + //////////////////////////////////////////////////////////////////////////////// + // comparison + + template + bool operator == (const foursome& left, const foursome& right); + template + bool operator < (const foursome& left, const foursome& right); + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#include "foursome.tpp" +#endif diff --git a/src/stlplus/containers/foursome.tpp b/src/stlplus/containers/foursome.tpp index b2bfe08..79017b9 100644 --- a/src/stlplus/containers/foursome.tpp +++ b/src/stlplus/containers/foursome.tpp @@ -1,59 +1,72 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton, from an original by Dan Milton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // the foursome class - - template - foursome::foursome(void) : - first(), second(), third(), fourth() - { - } - - template - foursome::foursome(const T1& p1, const T2& p2, const T3& p3, const T4& p4) : - first(p1), second(p2), third(p3), fourth(p4) - { - } - - template - foursome::foursome(const foursome& t2) : - first(t2.first), second(t2.second), third(t2.third), fourth(t2.fourth) - { - } - - //////////////////////////////////////////////////////////////////////////////// - // creation - - template - foursome make_foursome(const T1& first, const T2& second, const T3& third, const T4& fourth) - { - return foursome(first,second,third,fourth); - } - - //////////////////////////////////////////////////////////////////////////////// - // comparison - - template - bool operator == (const foursome& left, const foursome& right) - { - // foursomes are equal if all elements are equal - return - left.first == right.first && - left.second == right.second && - left.third == right.third && - left.fourth == right.fourth; - } - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton, from an original by Dan Milton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // the foursome class + + template + foursome::foursome(void) : + first(), second(), third(), fourth() + { + } + + template + foursome::foursome(const T1& p1, const T2& p2, const T3& p3, const T4& p4) : + first(p1), second(p2), third(p3), fourth(p4) + { + } + + template + foursome::foursome(const foursome& t2) : + first(t2.first), second(t2.second), third(t2.third), fourth(t2.fourth) + { + } + + //////////////////////////////////////////////////////////////////////////////// + // creation + + template + foursome make_foursome(const T1& first, const T2& second, const T3& third, const T4& fourth) + { + return foursome(first,second,third,fourth); + } + + //////////////////////////////////////////////////////////////////////////////// + // comparison + + template + bool operator == (const foursome& left, const foursome& right) + { + // foursomes are equal if all elements are equal + return + left.first == right.first && + left.second == right.second && + left.third == right.third && + left.fourth == right.fourth; + } + + template + bool operator < (const foursome& left, const foursome& right) + { + // use the < operator on each element + return left.first < right.first ? true : + right.first < left.first ? false : + left.second < right.second ? true : + right.second < left.second ? false : + left.third < right.third ? true : + right.third < left.third ? false : + left.fourth < right.fourth; + } + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus diff --git a/src/stlplus/containers/hash.hpp b/src/stlplus/containers/hash.hpp index 13e9541..f85480b 100644 --- a/src/stlplus/containers/hash.hpp +++ b/src/stlplus/containers/hash.hpp @@ -1,205 +1,205 @@ -#ifndef STLPLUS_HASH -#define STLPLUS_HASH -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// A chained hash table using STL semantics - -//////////////////////////////////////////////////////////////////////////////// -#include "containers_fixes.hpp" -#include "exceptions.hpp" -#include "safe_iterator.hpp" -#include -#include - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // internals - - template class hash; - template class hash_element; - - //////////////////////////////////////////////////////////////////////////////// - // iterator class - - template - class hash_iterator : public safe_iterator,hash_element > - { - public: - friend class hash; - - // local type definitions - // an iterator points to a value whilst a const_iterator points to a const value - typedef V value_type; - typedef hash_iterator > iterator; - typedef hash_iterator > const_iterator; - typedef hash_iterator this_iterator; - typedef V& reference; - typedef V* pointer; - - // constructor to create a null iterator - you must assign a valid value to this iterator before using it - // any attempt to dereference or use a null iterator is an error - // the only valid thing you can do is assign an iterator to it - hash_iterator(void); - ~hash_iterator(void); - - // Type conversion methods allow const_iterator and iterator to be converted - // convert an iterator/const_iterator to a const_iterator - const_iterator constify(void) const; - // convert an iterator/const_iterator to an iterator - iterator deconstify(void) const; - - // increment operators used to step through the set of all values in a hash - // it is only legal to increment a valid iterator - // there's no decrement - I've only implemented this as a unidirectional iterator - // pre-increment - this_iterator& operator ++ (void) - throw(null_dereference,end_dereference); - // post-increment - this_iterator operator ++ (int) - throw(null_dereference,end_dereference); - - // test useful for testing whether iteration has completed - bool operator == (const this_iterator& r) const; - bool operator != (const this_iterator& r) const; - bool operator < (const this_iterator& r) const; - - // access the value - a const_iterator gives you a const value, an iterator a non-const value - // it is illegal to dereference an invalid (i.e. null or end) iterator - reference operator*(void) const - throw(null_dereference,end_dereference); - pointer operator->(void) const - throw(null_dereference,end_dereference); - - private: - friend class hash_element; - - // constructor used by hash to create a non-null iterator - // you cannot create a valid iterator except by calling a hash method that returns one - explicit hash_iterator(hash_element* element); - // constructor used to create an end iterator - explicit hash_iterator(const hash* owner); - // used to create an alias of an iterator - explicit hash_iterator(const safe_iterator, hash_element >& iterator); - }; - - //////////////////////////////////////////////////////////////////////////////// - // Hash class - // K = key type - // T = value type - // H = hash function object with the profile 'unsigned H(const K&)' - // E = equal function object with profile 'bool E(const K&, const K&)' defaults to equal_to which in turn calls '==' - - template > - class hash - { - public: - typedef unsigned size_type; - typedef K key_type; - typedef T data_type; - typedef T mapped_type; - typedef std::pair value_type; - typedef hash_iterator iterator; - typedef hash_iterator const_iterator; - - // construct a hash table with specified number of bins - // the default 0 bins means leave it to the table to decide - // specifying 0 bins also enables auto-rehashing, otherwise auto-rehashing defaults off - hash(unsigned bins = 0); - ~hash(void); - - // copy and equality copy the data elements but not the size of the copied table - hash(const hash&); - hash& operator = (const hash&); - - // test for an empty table and for the size of a table - // efficient because the size is stored separately from the table contents - bool empty(void) const; - unsigned size(void) const; - - // test for equality - two hashes are equal if they contain equal values - bool operator == (const hash&) const; - bool operator != (const hash&) const; - - // switch auto-rehash on - void auto_rehash(void); - // switch auto-rehash off - void manual_rehash(void); - // force a rehash now - // default of 0 means implement built-in size calculation for rehashing (recommended - it doubles the number of bins) - void rehash(unsigned bins = 0); - // test the loading ratio, which is the size divided by the number of bins - // use this if you are doing your own rehashing - // the recommendation is to double the bins when the loading exceeds 0.5 which is what auto-rehashing does - float loading(void) const; - - // test for the presence of a key - bool present(const K& key) const; - // provide map equivalent key count function (0 or 1, as not a multimap) - size_type count(const K& key) const; - - // insert a new key/data pair - replaces any previous value for this key - iterator insert(const K& key, const T& data); - // insert a copy of the pair into the table (std::map compatible) - std::pair insert(const value_type& value); - // insert a new key and return the iterator so that the data can be filled in - iterator insert(const K& key); - - // remove a key/data pair from the hash table - // as in map, this returns the number of elements erased - size_type erase(const K& key); - // remove an element from the hash table using an iterator - // as in map, returns an iterator to the next element - iterator erase(iterator it); - // remove all elements from the hash table - void erase(void); - // map equivalent of above - void clear(void); - - // find a key and return an iterator to it - // The iterator is like a pointer to a pair - // end() is returned if the find fails - const_iterator find(const K& key) const; - iterator find(const K& key); - - // returns the data corresponding to the key - // const version is used for const hashes and cannot change the hash, so failure causes an exception - // non-const version is for non-const hashes and is like map - it creates a new key/data pair if find fails - const T& operator[] (const K& key) const throw(std::out_of_range); - T& operator[] (const K& key); - - // iterators allow the hash table to be traversed - // iterators remain valid unless an item is removed or unless a rehash happens - const_iterator begin(void) const; - iterator begin(void); - const_iterator end(void) const; - iterator end(void); - - // diagnostic report shows the number of items in each bin so can be used - // to diagnose effectiveness of hash functions - void debug_report(std::ostream&) const; - - // internals - private: - friend class hash_element; - friend class hash_iterator >; - friend class hash_iterator >; - - unsigned m_rehash; - unsigned m_bins; - unsigned m_size; - hash_element** m_values; - }; - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#include "hash.tpp" -#endif +#ifndef STLPLUS_HASH +#define STLPLUS_HASH +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// A chained hash table using STL semantics + +//////////////////////////////////////////////////////////////////////////////// +#include "containers_fixes.hpp" +#include "exceptions.hpp" +#include "safe_iterator.hpp" +#include +#include + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // internals + + template class hash; + template class hash_element; + + //////////////////////////////////////////////////////////////////////////////// + // iterator class + + template + class hash_iterator : public safe_iterator,hash_element > + { + public: + friend class hash; + + // local type definitions + // an iterator points to a value whilst a const_iterator points to a const value + typedef V value_type; + typedef hash_iterator > iterator; + typedef hash_iterator > const_iterator; + typedef hash_iterator this_iterator; + typedef V& reference; + typedef V* pointer; + + // constructor to create a null iterator - you must assign a valid value to this iterator before using it + // any attempt to dereference or use a null iterator is an error + // the only valid thing you can do is assign an iterator to it + hash_iterator(void); + ~hash_iterator(void); + + // Type conversion methods allow const_iterator and iterator to be converted + // convert an iterator/const_iterator to a const_iterator + const_iterator constify(void) const; + // convert an iterator/const_iterator to an iterator + iterator deconstify(void) const; + + // increment operators used to step through the set of all values in a hash + // it is only legal to increment a valid iterator + // there's no decrement - I've only implemented this as a unidirectional iterator + // pre-increment + this_iterator& operator ++ (void) + throw(null_dereference,end_dereference); + // post-increment + this_iterator operator ++ (int) + throw(null_dereference,end_dereference); + + // test useful for testing whether iteration has completed + bool operator == (const this_iterator& r) const; + bool operator != (const this_iterator& r) const; + bool operator < (const this_iterator& r) const; + + // access the value - a const_iterator gives you a const value, an iterator a non-const value + // it is illegal to dereference an invalid (i.e. null or end) iterator + reference operator*(void) const + throw(null_dereference,end_dereference); + pointer operator->(void) const + throw(null_dereference,end_dereference); + + private: + friend class hash_element; + + // constructor used by hash to create a non-null iterator + // you cannot create a valid iterator except by calling a hash method that returns one + explicit hash_iterator(hash_element* element); + // constructor used to create an end iterator + explicit hash_iterator(const hash* owner); + // used to create an alias of an iterator + explicit hash_iterator(const safe_iterator, hash_element >& iterator); + }; + + //////////////////////////////////////////////////////////////////////////////// + // Hash class + // K = key type + // T = value type + // H = hash function object with the profile 'unsigned H(const K&)' + // E = equal function object with profile 'bool E(const K&, const K&)' defaults to equal_to which in turn calls '==' + + template > + class hash + { + public: + typedef unsigned size_type; + typedef K key_type; + typedef T data_type; + typedef T mapped_type; + typedef std::pair value_type; + typedef hash_iterator iterator; + typedef hash_iterator const_iterator; + + // construct a hash table with specified number of bins + // the default 0 bins means leave it to the table to decide + // specifying 0 bins also enables auto-rehashing, otherwise auto-rehashing defaults off + hash(unsigned bins = 0); + ~hash(void); + + // copy and equality copy the data elements but not the size of the copied table + hash(const hash&); + hash& operator = (const hash&); + + // test for an empty table and for the size of a table + // efficient because the size is stored separately from the table contents + bool empty(void) const; + unsigned size(void) const; + + // test for equality - two hashes are equal if they contain equal values + bool operator == (const hash&) const; + bool operator != (const hash&) const; + + // switch auto-rehash on + void auto_rehash(void); + // switch auto-rehash off + void manual_rehash(void); + // force a rehash now + // default of 0 means implement built-in size calculation for rehashing (recommended - it doubles the number of bins) + void rehash(unsigned bins = 0); + // test the loading ratio, which is the size divided by the number of bins + // use this if you are doing your own rehashing + // the recommendation is to double the bins when the loading exceeds 0.5 which is what auto-rehashing does + float loading(void) const; + + // test for the presence of a key + bool present(const K& key) const; + // provide map equivalent key count function (0 or 1, as not a multimap) + size_type count(const K& key) const; + + // insert a new key/data pair - replaces any previous value for this key + iterator insert(const K& key, const T& data); + // insert a copy of the pair into the table (std::map compatible) + std::pair insert(const value_type& value); + // insert a new key and return the iterator so that the data can be filled in + iterator insert(const K& key); + + // remove a key/data pair from the hash table + // as in map, this returns the number of elements erased + size_type erase(const K& key); + // remove an element from the hash table using an iterator + // as in map, returns an iterator to the next element + iterator erase(iterator it); + // remove all elements from the hash table + void erase(void); + // map equivalent of above + void clear(void); + + // find a key and return an iterator to it + // The iterator is like a pointer to a pair + // end() is returned if the find fails + const_iterator find(const K& key) const; + iterator find(const K& key); + + // returns the data corresponding to the key + // const version is used for const hashes and cannot change the hash, so failure causes an exception + // non-const version is for non-const hashes and is like map - it creates a new key/data pair if find fails + const T& operator[] (const K& key) const throw(std::out_of_range); + T& operator[] (const K& key); + + // iterators allow the hash table to be traversed + // iterators remain valid unless an item is removed or unless a rehash happens + const_iterator begin(void) const; + iterator begin(void); + const_iterator end(void) const; + iterator end(void); + + // diagnostic report shows the number of items in each bin so can be used + // to diagnose effectiveness of hash functions + void debug_report(std::ostream&) const; + + // internals + private: + friend class hash_element; + friend class hash_iterator >; + friend class hash_iterator >; + + unsigned m_rehash; + unsigned m_bins; + unsigned m_size; + hash_element** m_values; + }; + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#include "hash.tpp" +#endif diff --git a/src/stlplus/containers/hash.tpp b/src/stlplus/containers/hash.tpp index da2e39d..dc83c0f 100644 --- a/src/stlplus/containers/hash.tpp +++ b/src/stlplus/containers/hash.tpp @@ -1,658 +1,658 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// -#include - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // the element stored in the hash - - template - class hash_element - { - public: - master_iterator, hash_element > m_master; - std::pair m_value; - hash_element* m_next; - unsigned m_hash; - - hash_element(const hash* owner, const K& key, const T& data, unsigned hash) : - m_master(owner,this), m_value(key,data), m_next(0), m_hash(hash) - { - } - - hash_element(const hash* owner, const std::pair& value, unsigned hash) : - m_master(owner,this), m_value(value), m_next(0), m_hash(hash) - { - } - - ~hash_element(void) - { - m_next = 0; - m_hash = 0; - } - - const hash* owner(void) const - { - return m_master.owner(); - } - - // generate the bin number from the hash value and the owner's number of bins - unsigned bin(void) const - { - return m_hash % (owner()->m_bins); - } - }; - - //////////////////////////////////////////////////////////////////////////////// - // iterator - - // null constructor - template - hash_iterator::hash_iterator(void) - { - } - - // non-null constructor used from within the hash to construct a valid iterator - template - hash_iterator::hash_iterator(hash_element* element) : - safe_iterator,hash_element >(element->m_master) - { - } - - // constructor used to create an end iterator - template - hash_iterator::hash_iterator(const hash* owner) : - safe_iterator,hash_element >(owner) - { - } - - template - hash_iterator::hash_iterator(const safe_iterator, hash_element >& iterator) : - safe_iterator,hash_element >(iterator) - { - } - - // destructor - - template - hash_iterator::~hash_iterator(void) - { - } - - // mode conversions - - template - TYPENAME hash_iterator::const_iterator hash_iterator::constify(void) const - { - return hash_iterator >(*this); - } - - template - TYPENAME hash_iterator::iterator hash_iterator::deconstify(void) const - { - return hash_iterator >(*this); - } - - // increment operator looks for the next element in the table - // if there isn't one, then this becomes an end() iterator with m_bin = m_bins - template - TYPENAME hash_iterator::this_iterator& hash_iterator::operator ++ (void) - throw(null_dereference,end_dereference) - { - this->assert_valid(); - if (this->node()->m_next) - set(this->node()->m_next->m_master); - else - { - // failing that, subsequent hash values are tried until either an element is found or there are no more bins - // in which case it becomes an end() iterator - hash_element* element = 0; - unsigned current_bin = this->node()->bin(); - for(current_bin++; !element && (current_bin < this->owner()->m_bins); current_bin++) - element = this->owner()->m_values[current_bin]; - if (element) - set(element->m_master); - else - this->set_end(); - } - return *this; - } - - // post-increment is defined in terms of pre-increment - template - TYPENAME hash_iterator::this_iterator hash_iterator::operator ++ (int) - throw(null_dereference,end_dereference) - { - hash_iterator old(*this); - ++(*this); - return old; - } - - // two iterators are equal if they point to the same element - // both iterators must be non-null and belong to the same table - template - bool hash_iterator::operator == (const hash_iterator& r) const - { - return equal(r); - } - - template - bool hash_iterator::operator != (const hash_iterator& r) const - { - return !operator==(r); - } - - template - bool hash_iterator::operator < (const hash_iterator& r) const - { - return compare(r) < 0; - } - - // iterator dereferencing is only legal on a non-null iterator - template - V& hash_iterator::operator*(void) const - throw(null_dereference,end_dereference) - { - this->assert_valid(); - return this->node()->m_value; - } - - template - V* hash_iterator::operator->(void) const - throw(null_dereference,end_dereference) - { - return &(operator*()); - } - - //////////////////////////////////////////////////////////////////////////////// - // hash - - // totally arbitrary initial size used for auto-rehashed tables - static unsigned hash_default_bins = 127; - - // constructor - // tests whether the user wants auto-rehash - // sets the rehash point to be a loading of 1.0 by setting it to the number of bins - // uses the user's size unless this is zero, in which case implement the default - - template - hash::hash(unsigned bins) : - m_rehash(bins), m_bins(bins > 0 ? bins : hash_default_bins), m_size(0), m_values(0) - { - m_values = new hash_element*[m_bins]; - for (unsigned i = 0; i < m_bins; i++) - m_values[i] = 0; - } - - template - hash::~hash(void) - { - // delete all the elements - clear(); - // and delete the data structure - delete[] m_values; - m_values = 0; - } - - // as usual, implement the copy constructor i.t.o. the assignment operator - - template - hash::hash(const hash& right) : - m_rehash(right.m_rehash), m_bins(right.m_bins), m_size(0), m_values(0) - { - m_values = new hash_element*[right.m_bins]; - // copy the rehash behaviour as well as the size - for (unsigned i = 0; i < m_bins; i++) - m_values[i] = 0; - *this = right; - } - - // assignment operator - // this is done by copying the elements - // the source and target hashes can be different sizes - // the hash is self-copy safe, i.e. it is legal to say x = x; - - template - hash& hash::operator = (const hash& r) - { - // make self-copy safe - if (&r == this) return *this; - // remove all the existing elements - clear(); - // copy the elements across - remember that this is rehashing because the two - // tables can be different sizes so there is no quick way of doing this by - // copying the lists - for (hash_iterator > i = r.begin(); i != r.end(); ++i) - insert(i->first, i->second); - return *this; - } - - // number of values in the hash - template - bool hash::empty(void) const - { - return m_size == 0; - } - - template - unsigned hash::size(void) const - { - return m_size; - } - - // equality - template - bool hash::operator == (const hash& right) const - { - // this table is the same as the right table if they are the same table! - if (&right == this) return true; - // they must be the same size to be equal - if (m_size != right.m_size) return false; - // now every key in this must be in right and have the same data - for (hash_iterator > i = begin(); i != end(); i++) - { - hash_iterator > found = right.find(i->first); - if (found == right.end()) return false; - if (!(i->second == found->second)) return false; - } - return true; - } - - // set up the hash to auto-rehash at a specific size - // setting the rehash size to 0 forces manual rehashing - template - void hash::auto_rehash(void) - { - m_rehash = m_bins; - } - - template - void hash::manual_rehash(void) - { - m_rehash = 0; - } - - // the rehash function - // builds a new hash table and moves the elements (without copying) from the old to the new - // I store the un-modulused hash value in the element for more efficient rehashing - // passing 0 to the bins parameter does auto-rehashing - // passing any other value forces the number of bins - - template - void hash::rehash(unsigned bins) - { - // user specified size: just take the user's value - // auto calculate: if the load is high, increase the size; else do nothing - unsigned new_bins = bins ? bins : m_bins; - if (bins == 0 && m_size > 0) - { - // these numbers are pretty arbitrary - // TODO - make them user-customisable? - float load = loading(); - if (load > 2.0) - new_bins = (unsigned)(m_bins * load); - else if (load > 1.0) - new_bins = m_bins * 2; - } - if (new_bins == m_bins) return; - // set the new rehashing point if auto-rehashing is on - if (m_rehash) m_rehash = new_bins; - // move aside the old structure - hash_element** old_values = m_values; - unsigned old_bins = m_bins; - // create a replacement structure - m_values = new hash_element*[new_bins]; - for (unsigned i = 0; i < new_bins; i++) - m_values[i] = 0; - m_bins = new_bins; - // move all the old elements across, rehashing each one - for (unsigned j = 0; j < old_bins; j++) - { - while(old_values[j]) - { - // unhook from the old structure - hash_element* current = old_values[j]; - old_values[j] = current->m_next; - // rehash using the stored hash value - unsigned bin = current->bin(); - // hook it into the new structure - current->m_next = m_values[bin]; - m_values[bin] = current; - } - } - // now delete the old structure - delete[] old_values; - } - - // the loading is the average number of elements per bin - // this simplifies to the total elements divided by the number of bins - - template - float hash::loading(void) const - { - return (float)m_size / (float)m_bins; - } - - // remove all elements from the table - - template - void hash::erase(void) - { - // unhook the list elements and destroy them - for (unsigned i = 0; i < m_bins; i++) - { - hash_element* current = m_values[i]; - while(current) - { - hash_element* next = current->m_next; - delete current; - current = next; - } - m_values[i] = 0; - } - m_size = 0; - } - - // test for whether a key is present in the table - - template - bool hash::present(const K& key) const - { - return find(key) != end(); - } - - template - TYPENAME hash::size_type hash::count(const K& key) const - { - return present() ? 1 : 0; - } - - // add a key and data element to the table - defined in terms of the general-purpose pair insert function - - template - TYPENAME hash::iterator hash::insert(const K& key, const T& data) - { - return insert(std::pair(key,data)).first; - } - - // insert a key/data pair into the table - // this removes any old value with the same key since there is no multihash functionality - - template - std::pair::iterator, bool> hash::insert(const std::pair& value) - { - // if auto-rehash is enabled, implement the auto-rehash before inserting the new value - // the table is rehashed if this insertion makes the loading exceed 1.0 - if (m_rehash && (m_size >= m_rehash)) rehash(); - // calculate the new hash value - unsigned hash_value_full = H()(value.first); - unsigned bin = hash_value_full % m_bins; - bool inserted = true; - // unhook any previous value with this key - // this has been inlined from erase(key) so that the hash value is not calculated twice - hash_element* previous = 0; - for (hash_element* current = m_values[bin]; current; previous = current, current = current->m_next) - { - // first check the full stored hash value - if (current->m_hash != hash_value_full) continue; - - // next try the equality operator - if (!E()(current->m_value.first, value.first)) continue; - - // unhook this value and destroy it - if (previous) - previous->m_next = current->m_next; - else - m_values[bin] = current->m_next; - delete current; - m_size--; - - // we've overwritten a previous value - inserted = false; - - // assume there can only be one match so we can give up now - break; - } - // now hook in a new list element at the start of the list for this hash value - hash_element* new_item = new hash_element(this, value, hash_value_full); - new_item->m_next = m_values[bin]; - m_values[bin] = new_item; - // increment the size count - m_size++; - // construct an iterator from the list node, and return whether inserted - return std::make_pair(hash_iterator >(new_item), inserted); - } - - // insert a key with an empty data field ready to be filled in later - - template - TYPENAME hash::iterator hash::insert(const K& key) - { - return insert(key,T()); - } - - // remove a key from the table - return true if the key was found and removed, false if it wasn't present - - template - unsigned hash::erase(const K& key) - { - unsigned hash_value_full = H()(key); - unsigned bin = hash_value_full % m_bins; - // scan the list for an element with this key - // need to keep a previous pointer because the lists are single-linked - hash_element* previous = 0; - for (hash_element* current = m_values[bin]; current; previous = current, current = current->m_next) - { - // first check the full stored hash value - if (current->m_hash != hash_value_full) continue; - - // next try the equality operator - if (!E()(current->m_value.first, key)) continue; - - // found this key, so unhook the element from the list - if (previous) - previous->m_next = current->m_next; - else - m_values[bin] = current->m_next; - // destroy it - delete current; - // remember to maintain the size count - m_size--; - return 1; - } - return 0; - } - - // remove an element from the hash table using an iterator (std::map equivalent) - template - TYPENAME hash::iterator hash::erase(TYPENAME hash::iterator it) - { - // work out what the next iterator is in order to return it later - TYPENAME hash::iterator next(it); - ++next; - // we now need to find where this item is - made difficult by the use of - // single-linked lists which means I have to search through the bin from - // the top in order to unlink from the list. - unsigned hash_value_full = it.node()->m_hash; - unsigned bin = hash_value_full % m_bins; - // scan the list for this element - // need to keep a previous pointer because the lists are single-linked - hash_element* previous = 0; - for (hash_element* current = m_values[bin]; current; previous = current, current = current->m_next) - { - // direct test on the address of the element - if (current != it.node()) continue; - // found this iterator, so unhook the element from the list - if (previous) - previous->m_next = current->m_next; - else - m_values[bin] = current->m_next; - // destroy it - delete current; - current = 0; - // remember to maintain the size count - m_size--; - break; - } - return next; - } - - template - void hash::clear(void) - { - erase(); - } - - // search for a key in the table and return an iterator to it - // if the search fails, returns an end() iterator - - template - TYPENAME hash::const_iterator hash::find(const K& key) const - { - // scan the list for this key's hash value for the element with a matching key - unsigned hash_value_full = H()(key); - unsigned bin = hash_value_full % m_bins; - for (hash_element* current = m_values[bin]; current; current = current->m_next) - { - if (current->m_hash == hash_value_full && E()(current->m_value.first, key)) - return hash_iterator >(current); - } - return end(); - } - - template - TYPENAME hash::iterator hash::find(const K& key) - { - // scan the list for this key's hash value for the element with a matching key - unsigned hash_value_full = H()(key); - unsigned bin = hash_value_full % m_bins; - for (hash_element* current = m_values[bin]; current; current = current->m_next) - { - if (current->m_hash == hash_value_full && E()(current->m_value.first, key)) - return hash_iterator >(current); - } - return end(); - } - - // table lookup by key using the index operator[], returning a reference to the data field, not an iterator - // this is rather like the std::map's [] operator - // the difference is that I have a const and non-const version - // the const version will not create the element if not present already, but the non-const version will - // the non-const version is compatible with the behaviour of the map - - template - const T& hash::operator[] (const K& key) const throw(std::out_of_range) - { - // this const version cannot change the hash, so has to raise an exception if the key is missing - hash_iterator > found = find(key); - if (found == end()) - throw std::out_of_range("key not found in stlplus::hash::operator[]"); - return found->second; - } - - template - T& hash::operator[] (const K& key) - { - // this non-const version can change the hash, so creates a new element if the key is missing - hash_iterator > found = find(key); - if (found == end()) - found = insert(key); - return found->second; - } - - // iterators - - template - TYPENAME hash::const_iterator hash::begin(void) const - { - // find the first element - for (unsigned bin = 0; bin < m_bins; bin++) - if (m_values[bin]) - return hash_iterator >(m_values[bin]); - // if the hash is empty, return the end iterator - return end(); - } - - template - TYPENAME hash::iterator hash::begin(void) - { - // find the first element - for (unsigned bin = 0; bin < m_bins; bin++) - if (m_values[bin]) - return hash_iterator >(m_values[bin]); - // if the hash is empty, return the end iterator - return end(); - } - - template - TYPENAME hash::const_iterator hash::end(void) const - { - return hash_iterator >(this); - } - - template - TYPENAME hash::iterator hash::end(void) - { - return hash_iterator >(this); - } - - template - void hash::debug_report(std::ostream& str) const - { - // calculate some stats first - unsigned occupied = 0; - unsigned min_in_bin = m_size; - unsigned max_in_bin = 0; - for (unsigned i = 0; i < m_bins; i++) - { - if (m_values[i]) occupied++; - unsigned count = 0; - for (hash_element* item = m_values[i]; item; item = item->m_next) count++; - if (count > max_in_bin) max_in_bin = count; - if (count < min_in_bin) min_in_bin = count; - } - // now print the table - str << "------------------------------------------------------------------------" << std::endl; - str << "| size: " << m_size << std::endl; - str << "| bins: " << m_bins << std::endl; - str << "| loading: " << loading() << " "; - if (m_rehash) - str << "auto-rehash at " << m_rehash << std::endl; - else - str << "manual rehash" << std::endl; - str << "| occupied: " << occupied - << std::fixed << " (" << (100.0*(float)occupied/(float)m_bins) << "%)" << std::scientific - << ", min = " << min_in_bin << ", max = " << max_in_bin << std::endl; - str << "|-----------------------------------------------------------------------" << std::endl; - str << "| bin 0 1 2 3 4 5 6 7 8 9" << std::endl; - str << "| ---------------------------------------------------------------"; - for (unsigned j = 0; j < m_bins; j++) - { - if (j % 10 == 0) - { - str << std::endl; - str << "| " << std::setw(6) << std::right << (j/10*10) << std::left << " |"; - } - unsigned count = 0; - for (hash_element* item = m_values[j]; item; item = item->m_next) count++; - if (!count) - str << " ."; - else - str << std::setw(6) << std::right << count << std::left; - } - str << std::endl; - str << "------------------------------------------------------------------------" << std::endl; - } - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// +#include + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // the element stored in the hash + + template + class hash_element + { + public: + master_iterator, hash_element > m_master; + std::pair m_value; + hash_element* m_next; + unsigned m_hash; + + hash_element(const hash* owner, const K& key, const T& data, unsigned hash) : + m_master(owner,this), m_value(key,data), m_next(0), m_hash(hash) + { + } + + hash_element(const hash* owner, const std::pair& value, unsigned hash) : + m_master(owner,this), m_value(value), m_next(0), m_hash(hash) + { + } + + ~hash_element(void) + { + m_next = 0; + m_hash = 0; + } + + const hash* owner(void) const + { + return m_master.owner(); + } + + // generate the bin number from the hash value and the owner's number of bins + unsigned bin(void) const + { + return m_hash % (owner()->m_bins); + } + }; + + //////////////////////////////////////////////////////////////////////////////// + // iterator + + // null constructor + template + hash_iterator::hash_iterator(void) + { + } + + // non-null constructor used from within the hash to construct a valid iterator + template + hash_iterator::hash_iterator(hash_element* element) : + safe_iterator,hash_element >(element->m_master) + { + } + + // constructor used to create an end iterator + template + hash_iterator::hash_iterator(const hash* owner) : + safe_iterator,hash_element >(owner) + { + } + + template + hash_iterator::hash_iterator(const safe_iterator, hash_element >& iterator) : + safe_iterator,hash_element >(iterator) + { + } + + // destructor + + template + hash_iterator::~hash_iterator(void) + { + } + + // mode conversions + + template + TYPENAME hash_iterator::const_iterator hash_iterator::constify(void) const + { + return hash_iterator >(*this); + } + + template + TYPENAME hash_iterator::iterator hash_iterator::deconstify(void) const + { + return hash_iterator >(*this); + } + + // increment operator looks for the next element in the table + // if there isn't one, then this becomes an end() iterator with m_bin = m_bins + template + TYPENAME hash_iterator::this_iterator& hash_iterator::operator ++ (void) + throw(null_dereference,end_dereference) + { + this->assert_valid(); + if (this->node()->m_next) + set(this->node()->m_next->m_master); + else + { + // failing that, subsequent hash values are tried until either an element is found or there are no more bins + // in which case it becomes an end() iterator + hash_element* element = 0; + unsigned current_bin = this->node()->bin(); + for(current_bin++; !element && (current_bin < this->owner()->m_bins); current_bin++) + element = this->owner()->m_values[current_bin]; + if (element) + set(element->m_master); + else + this->set_end(); + } + return *this; + } + + // post-increment is defined in terms of pre-increment + template + TYPENAME hash_iterator::this_iterator hash_iterator::operator ++ (int) + throw(null_dereference,end_dereference) + { + hash_iterator old(*this); + ++(*this); + return old; + } + + // two iterators are equal if they point to the same element + // both iterators must be non-null and belong to the same table + template + bool hash_iterator::operator == (const hash_iterator& r) const + { + return equal(r); + } + + template + bool hash_iterator::operator != (const hash_iterator& r) const + { + return !operator==(r); + } + + template + bool hash_iterator::operator < (const hash_iterator& r) const + { + return compare(r) < 0; + } + + // iterator dereferencing is only legal on a non-null iterator + template + V& hash_iterator::operator*(void) const + throw(null_dereference,end_dereference) + { + this->assert_valid(); + return this->node()->m_value; + } + + template + V* hash_iterator::operator->(void) const + throw(null_dereference,end_dereference) + { + return &(operator*()); + } + + //////////////////////////////////////////////////////////////////////////////// + // hash + + // totally arbitrary initial size used for auto-rehashed tables + static unsigned hash_default_bins = 127; + + // constructor + // tests whether the user wants auto-rehash + // sets the rehash point to be a loading of 1.0 by setting it to the number of bins + // uses the user's size unless this is zero, in which case implement the default + + template + hash::hash(unsigned bins) : + m_rehash(bins), m_bins(bins > 0 ? bins : hash_default_bins), m_size(0), m_values(0) + { + m_values = new hash_element*[m_bins]; + for (unsigned i = 0; i < m_bins; i++) + m_values[i] = 0; + } + + template + hash::~hash(void) + { + // delete all the elements + clear(); + // and delete the data structure + delete[] m_values; + m_values = 0; + } + + // as usual, implement the copy constructor i.t.o. the assignment operator + + template + hash::hash(const hash& right) : + m_rehash(right.m_rehash), m_bins(right.m_bins), m_size(0), m_values(0) + { + m_values = new hash_element*[right.m_bins]; + // copy the rehash behaviour as well as the size + for (unsigned i = 0; i < m_bins; i++) + m_values[i] = 0; + *this = right; + } + + // assignment operator + // this is done by copying the elements + // the source and target hashes can be different sizes + // the hash is self-copy safe, i.e. it is legal to say x = x; + + template + hash& hash::operator = (const hash& r) + { + // make self-copy safe + if (&r == this) return *this; + // remove all the existing elements + clear(); + // copy the elements across - remember that this is rehashing because the two + // tables can be different sizes so there is no quick way of doing this by + // copying the lists + for (hash_iterator > i = r.begin(); i != r.end(); ++i) + insert(i->first, i->second); + return *this; + } + + // number of values in the hash + template + bool hash::empty(void) const + { + return m_size == 0; + } + + template + unsigned hash::size(void) const + { + return m_size; + } + + // equality + template + bool hash::operator == (const hash& right) const + { + // this table is the same as the right table if they are the same table! + if (&right == this) return true; + // they must be the same size to be equal + if (m_size != right.m_size) return false; + // now every key in this must be in right and have the same data + for (hash_iterator > i = begin(); i != end(); i++) + { + hash_iterator > found = right.find(i->first); + if (found == right.end()) return false; + if (!(i->second == found->second)) return false; + } + return true; + } + + // set up the hash to auto-rehash at a specific size + // setting the rehash size to 0 forces manual rehashing + template + void hash::auto_rehash(void) + { + m_rehash = m_bins; + } + + template + void hash::manual_rehash(void) + { + m_rehash = 0; + } + + // the rehash function + // builds a new hash table and moves the elements (without copying) from the old to the new + // I store the un-modulused hash value in the element for more efficient rehashing + // passing 0 to the bins parameter does auto-rehashing + // passing any other value forces the number of bins + + template + void hash::rehash(unsigned bins) + { + // user specified size: just take the user's value + // auto calculate: if the load is high, increase the size; else do nothing + unsigned new_bins = bins ? bins : m_bins; + if (bins == 0 && m_size > 0) + { + // these numbers are pretty arbitrary + // TODO - make them user-customisable? + float load = loading(); + if (load > 2.0) + new_bins = (unsigned)(m_bins * load); + else if (load > 1.0) + new_bins = m_bins * 2; + } + if (new_bins == m_bins) return; + // set the new rehashing point if auto-rehashing is on + if (m_rehash) m_rehash = new_bins; + // move aside the old structure + hash_element** old_values = m_values; + unsigned old_bins = m_bins; + // create a replacement structure + m_values = new hash_element*[new_bins]; + for (unsigned i = 0; i < new_bins; i++) + m_values[i] = 0; + m_bins = new_bins; + // move all the old elements across, rehashing each one + for (unsigned j = 0; j < old_bins; j++) + { + while(old_values[j]) + { + // unhook from the old structure + hash_element* current = old_values[j]; + old_values[j] = current->m_next; + // rehash using the stored hash value + unsigned bin = current->bin(); + // hook it into the new structure + current->m_next = m_values[bin]; + m_values[bin] = current; + } + } + // now delete the old structure + delete[] old_values; + } + + // the loading is the average number of elements per bin + // this simplifies to the total elements divided by the number of bins + + template + float hash::loading(void) const + { + return (float)m_size / (float)m_bins; + } + + // remove all elements from the table + + template + void hash::erase(void) + { + // unhook the list elements and destroy them + for (unsigned i = 0; i < m_bins; i++) + { + hash_element* current = m_values[i]; + while(current) + { + hash_element* next = current->m_next; + delete current; + current = next; + } + m_values[i] = 0; + } + m_size = 0; + } + + // test for whether a key is present in the table + + template + bool hash::present(const K& key) const + { + return find(key) != end(); + } + + template + TYPENAME hash::size_type hash::count(const K& key) const + { + return present() ? 1 : 0; + } + + // add a key and data element to the table - defined in terms of the general-purpose pair insert function + + template + TYPENAME hash::iterator hash::insert(const K& key, const T& data) + { + return insert(std::pair(key,data)).first; + } + + // insert a key/data pair into the table + // this removes any old value with the same key since there is no multihash functionality + + template + std::pair::iterator, bool> hash::insert(const std::pair& value) + { + // if auto-rehash is enabled, implement the auto-rehash before inserting the new value + // the table is rehashed if this insertion makes the loading exceed 1.0 + if (m_rehash && (m_size >= m_rehash)) rehash(); + // calculate the new hash value + unsigned hash_value_full = H()(value.first); + unsigned bin = hash_value_full % m_bins; + bool inserted = true; + // unhook any previous value with this key + // this has been inlined from erase(key) so that the hash value is not calculated twice + hash_element* previous = 0; + for (hash_element* current = m_values[bin]; current; previous = current, current = current->m_next) + { + // first check the full stored hash value + if (current->m_hash != hash_value_full) continue; + + // next try the equality operator + if (!E()(current->m_value.first, value.first)) continue; + + // unhook this value and destroy it + if (previous) + previous->m_next = current->m_next; + else + m_values[bin] = current->m_next; + delete current; + m_size--; + + // we've overwritten a previous value + inserted = false; + + // assume there can only be one match so we can give up now + break; + } + // now hook in a new list element at the start of the list for this hash value + hash_element* new_item = new hash_element(this, value, hash_value_full); + new_item->m_next = m_values[bin]; + m_values[bin] = new_item; + // increment the size count + m_size++; + // construct an iterator from the list node, and return whether inserted + return std::make_pair(hash_iterator >(new_item), inserted); + } + + // insert a key with an empty data field ready to be filled in later + + template + TYPENAME hash::iterator hash::insert(const K& key) + { + return insert(key,T()); + } + + // remove a key from the table - return true if the key was found and removed, false if it wasn't present + + template + unsigned hash::erase(const K& key) + { + unsigned hash_value_full = H()(key); + unsigned bin = hash_value_full % m_bins; + // scan the list for an element with this key + // need to keep a previous pointer because the lists are single-linked + hash_element* previous = 0; + for (hash_element* current = m_values[bin]; current; previous = current, current = current->m_next) + { + // first check the full stored hash value + if (current->m_hash != hash_value_full) continue; + + // next try the equality operator + if (!E()(current->m_value.first, key)) continue; + + // found this key, so unhook the element from the list + if (previous) + previous->m_next = current->m_next; + else + m_values[bin] = current->m_next; + // destroy it + delete current; + // remember to maintain the size count + m_size--; + return 1; + } + return 0; + } + + // remove an element from the hash table using an iterator (std::map equivalent) + template + TYPENAME hash::iterator hash::erase(TYPENAME hash::iterator it) + { + // work out what the next iterator is in order to return it later + TYPENAME hash::iterator next(it); + ++next; + // we now need to find where this item is - made difficult by the use of + // single-linked lists which means I have to search through the bin from + // the top in order to unlink from the list. + unsigned hash_value_full = it.node()->m_hash; + unsigned bin = hash_value_full % m_bins; + // scan the list for this element + // need to keep a previous pointer because the lists are single-linked + hash_element* previous = 0; + for (hash_element* current = m_values[bin]; current; previous = current, current = current->m_next) + { + // direct test on the address of the element + if (current != it.node()) continue; + // found this iterator, so unhook the element from the list + if (previous) + previous->m_next = current->m_next; + else + m_values[bin] = current->m_next; + // destroy it + delete current; + current = 0; + // remember to maintain the size count + m_size--; + break; + } + return next; + } + + template + void hash::clear(void) + { + erase(); + } + + // search for a key in the table and return an iterator to it + // if the search fails, returns an end() iterator + + template + TYPENAME hash::const_iterator hash::find(const K& key) const + { + // scan the list for this key's hash value for the element with a matching key + unsigned hash_value_full = H()(key); + unsigned bin = hash_value_full % m_bins; + for (hash_element* current = m_values[bin]; current; current = current->m_next) + { + if (current->m_hash == hash_value_full && E()(current->m_value.first, key)) + return hash_iterator >(current); + } + return end(); + } + + template + TYPENAME hash::iterator hash::find(const K& key) + { + // scan the list for this key's hash value for the element with a matching key + unsigned hash_value_full = H()(key); + unsigned bin = hash_value_full % m_bins; + for (hash_element* current = m_values[bin]; current; current = current->m_next) + { + if (current->m_hash == hash_value_full && E()(current->m_value.first, key)) + return hash_iterator >(current); + } + return end(); + } + + // table lookup by key using the index operator[], returning a reference to the data field, not an iterator + // this is rather like the std::map's [] operator + // the difference is that I have a const and non-const version + // the const version will not create the element if not present already, but the non-const version will + // the non-const version is compatible with the behaviour of the map + + template + const T& hash::operator[] (const K& key) const throw(std::out_of_range) + { + // this const version cannot change the hash, so has to raise an exception if the key is missing + hash_iterator > found = find(key); + if (found == end()) + throw std::out_of_range("key not found in stlplus::hash::operator[]"); + return found->second; + } + + template + T& hash::operator[] (const K& key) + { + // this non-const version can change the hash, so creates a new element if the key is missing + hash_iterator > found = find(key); + if (found == end()) + found = insert(key); + return found->second; + } + + // iterators + + template + TYPENAME hash::const_iterator hash::begin(void) const + { + // find the first element + for (unsigned bin = 0; bin < m_bins; bin++) + if (m_values[bin]) + return hash_iterator >(m_values[bin]); + // if the hash is empty, return the end iterator + return end(); + } + + template + TYPENAME hash::iterator hash::begin(void) + { + // find the first element + for (unsigned bin = 0; bin < m_bins; bin++) + if (m_values[bin]) + return hash_iterator >(m_values[bin]); + // if the hash is empty, return the end iterator + return end(); + } + + template + TYPENAME hash::const_iterator hash::end(void) const + { + return hash_iterator >(this); + } + + template + TYPENAME hash::iterator hash::end(void) + { + return hash_iterator >(this); + } + + template + void hash::debug_report(std::ostream& str) const + { + // calculate some stats first + unsigned occupied = 0; + unsigned min_in_bin = m_size; + unsigned max_in_bin = 0; + for (unsigned i = 0; i < m_bins; i++) + { + if (m_values[i]) occupied++; + unsigned count = 0; + for (hash_element* item = m_values[i]; item; item = item->m_next) count++; + if (count > max_in_bin) max_in_bin = count; + if (count < min_in_bin) min_in_bin = count; + } + // now print the table + str << "------------------------------------------------------------------------" << std::endl; + str << "| size: " << m_size << std::endl; + str << "| bins: " << m_bins << std::endl; + str << "| loading: " << loading() << " "; + if (m_rehash) + str << "auto-rehash at " << m_rehash << std::endl; + else + str << "manual rehash" << std::endl; + str << "| occupied: " << occupied + << std::fixed << " (" << (100.0*(float)occupied/(float)m_bins) << "%)" << std::scientific + << ", min = " << min_in_bin << ", max = " << max_in_bin << std::endl; + str << "|-----------------------------------------------------------------------" << std::endl; + str << "| bin 0 1 2 3 4 5 6 7 8 9" << std::endl; + str << "| ---------------------------------------------------------------"; + for (unsigned j = 0; j < m_bins; j++) + { + if (j % 10 == 0) + { + str << std::endl; + str << "| " << std::setw(6) << std::right << (j/10*10) << std::left << " |"; + } + unsigned count = 0; + for (hash_element* item = m_values[j]; item; item = item->m_next) count++; + if (!count) + str << " ."; + else + str << std::setw(6) << std::right << count << std::left; + } + str << std::endl; + str << "------------------------------------------------------------------------" << std::endl; + } + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + diff --git a/src/stlplus/containers/matrix.hpp b/src/stlplus/containers/matrix.hpp index 9a0fa69..ab6cce1 100644 --- a/src/stlplus/containers/matrix.hpp +++ b/src/stlplus/containers/matrix.hpp @@ -1,63 +1,63 @@ -#ifndef STLPLUS_MATRIX -#define STLPLUS_MATRIX -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// General-purpose 2D matrix data structure - -//////////////////////////////////////////////////////////////////////////////// -#include "containers_fixes.hpp" -#include - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - - template class matrix - { - public: - matrix(unsigned rows = 0, unsigned cols = 0, const T& fill = T()) throw(); - ~matrix(void) throw(); - - matrix(const matrix&) throw(); - matrix& operator =(const matrix&) throw(); - - void resize(unsigned rows, unsigned cols, const T& fill = T()) throw(); - - unsigned rows(void) const throw(); - unsigned columns(void) const throw(); - - void erase(const T& fill = T()) throw(); - void erase(unsigned row, unsigned col, const T& fill = T()) throw(std::out_of_range); - void insert(unsigned row, unsigned col, const T&) throw(std::out_of_range); - const T& item(unsigned row, unsigned col) const throw(std::out_of_range); - T& item(unsigned row, unsigned col) throw(std::out_of_range); - const T& operator()(unsigned row, unsigned col) const throw(std::out_of_range); - T& operator()(unsigned row, unsigned col) throw(std::out_of_range); - - void fill(const T& item = T()) throw(); - void fill_column(unsigned col, const T& item = T()) throw(std::out_of_range); - void fill_row(unsigned row, const T& item = T()) throw(std::out_of_range); - void fill_leading_diagonal(const T& item = T()) throw(); - void fill_trailing_diagonal(const T& item = T()) throw(); - void make_identity(const T& one, const T& zero = T()) throw(); - - void transpose(void) throw(); - - private: - unsigned m_rows; - unsigned m_cols; - T** m_data; - }; - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#include "matrix.tpp" -#endif +#ifndef STLPLUS_MATRIX +#define STLPLUS_MATRIX +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// General-purpose 2D matrix data structure + +//////////////////////////////////////////////////////////////////////////////// +#include "containers_fixes.hpp" +#include + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + + template class matrix + { + public: + matrix(unsigned rows = 0, unsigned cols = 0, const T& fill = T()) throw(); + ~matrix(void) throw(); + + matrix(const matrix&) throw(); + matrix& operator =(const matrix&) throw(); + + void resize(unsigned rows, unsigned cols, const T& fill = T()) throw(); + + unsigned rows(void) const throw(); + unsigned columns(void) const throw(); + + void erase(const T& fill = T()) throw(); + void erase(unsigned row, unsigned col, const T& fill = T()) throw(std::out_of_range); + void insert(unsigned row, unsigned col, const T&) throw(std::out_of_range); + const T& item(unsigned row, unsigned col) const throw(std::out_of_range); + T& item(unsigned row, unsigned col) throw(std::out_of_range); + const T& operator()(unsigned row, unsigned col) const throw(std::out_of_range); + T& operator()(unsigned row, unsigned col) throw(std::out_of_range); + + void fill(const T& item = T()) throw(); + void fill_column(unsigned col, const T& item = T()) throw(std::out_of_range); + void fill_row(unsigned row, const T& item = T()) throw(std::out_of_range); + void fill_leading_diagonal(const T& item = T()) throw(); + void fill_trailing_diagonal(const T& item = T()) throw(); + void make_identity(const T& one, const T& zero = T()) throw(); + + void transpose(void) throw(); + + private: + unsigned m_rows; + unsigned m_cols; + T** m_data; + }; + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#include "matrix.tpp" +#endif diff --git a/src/stlplus/containers/matrix.tpp b/src/stlplus/containers/matrix.tpp index 1e2f785..6bb6511 100644 --- a/src/stlplus/containers/matrix.tpp +++ b/src/stlplus/containers/matrix.tpp @@ -1,215 +1,215 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - - template - matrix::matrix(unsigned rows, unsigned cols, const T& fill) throw() - { - m_rows = 0; - m_cols = 0; - m_data = 0; - resize(rows,cols,fill); - } - - template - matrix::~matrix(void) throw() - { - for (unsigned row = 0; row < m_rows; row++) - delete[] m_data[row]; - delete[] m_data; - } - - template - matrix::matrix(const matrix& r) throw() - { - m_rows = 0; - m_cols = 0; - m_data = 0; - *this = r; - } - - template - matrix& matrix::operator =(const matrix& right) throw() - { - // clear the old values - for (unsigned row = 0; row < m_rows; row++) - delete[] m_data[row]; - delete[] m_data; - m_rows = 0; - m_cols = 0; - m_data = 0; - // now reconstruct with the new - resize(right.m_rows, right.m_cols); - for (unsigned row = 0; row < m_rows; row++) - for (unsigned col = 0; col < m_cols; col++) - m_data[row][col] = right.m_data[row][col]; - return *this; - } - - template - void matrix::resize(unsigned rows, unsigned cols, const T& fill) throw() - { - // a grid is an array of rows, where each row is an array of T - // a zero-row or zero-column matrix has a null grid - // TODO - make this exception-safe - new could throw here and that would cause a memory leak - T** new_grid = 0; - if (rows && cols) - { - new_grid = new T*[rows]; - for (unsigned row = 0; row < rows; row++) - { - new_grid[row] = new T[cols]; - // copy old items to the new grid but only within the bounds of the intersection of the old and new grids - // fill the rest of the grid with the initial value - for (unsigned col = 0; col < cols; col++) - if (row < m_rows && col < m_cols) - new_grid[row][col] = m_data[row][col]; - else - new_grid[row][col] = fill; - } - } - // destroy the old grid - for (unsigned row = 0; row < m_rows; row++) - delete[] m_data[row]; - delete[] m_data; - // move the new data into the matrix - m_data = new_grid; - m_rows = rows; - m_cols = cols; - } - - template - unsigned matrix::rows(void) const throw() - { - return m_rows; - } - - template - unsigned matrix::columns(void) const throw() - { - return m_cols; - } - - template - void matrix::erase(const T& fill) throw() - { - for (unsigned row = 0; row < m_rows; row++) - for (unsigned col = 0; col < m_cols; col++) - insert(row,col,fill); - } - - template - void matrix::erase(unsigned row, unsigned col, const T& fill) throw(std::out_of_range) - { - insert(row,col,fill); - } - - template - void matrix::insert(unsigned row, unsigned col, const T& element) throw(std::out_of_range) - { - if (row >= m_rows) throw std::out_of_range("matrix::insert row"); - if (col >= m_cols) throw std::out_of_range("matrix::insert col"); - m_data[row][col] = element; - } - - template - const T& matrix::item(unsigned row, unsigned col) const throw(std::out_of_range) - { - if (row >= m_rows) throw std::out_of_range("matrix::item row"); - if (col >= m_cols) throw std::out_of_range("matrix::item col"); - return m_data[row][col]; - } - - template - T& matrix::item(unsigned row, unsigned col) throw(std::out_of_range) - { - if (row >= m_rows) throw std::out_of_range("matrix::item row"); - if (col >= m_cols) throw std::out_of_range("matrix::item col"); - return m_data[row][col]; - } - - template - const T& matrix::operator()(unsigned row, unsigned col) const throw(std::out_of_range) - { - if (row >= m_rows) throw std::out_of_range("matrix::operator() row"); - if (col >= m_cols) throw std::out_of_range("matrix::operator() col"); - return m_data[row][col]; - } - - template - T& matrix::operator()(unsigned row, unsigned col) throw(std::out_of_range) - { - if (row >= m_rows) throw std::out_of_range("matrix::operator() row"); - if (col >= m_cols) throw std::out_of_range("matrix::operator() col"); - return m_data[row][col]; - } - - template - void matrix::fill(const T& item) throw() - { - erase(item); - } - - template - void matrix::fill_column(unsigned col, const T& item) throw (std::out_of_range) - { - if (col >= m_cols) throw std::out_of_range("matrix::fill_column"); - for (unsigned row = 0; row < m_rows; row++) - insert(row, col, item); - } - - template - void matrix::fill_row(unsigned row, const T& item) throw (std::out_of_range) - { - if (row >= m_rows) throw std::out_of_range("matrix::fill_row"); - for (unsigned col = 0; col < m_cols; col++) - insert(row, col, item); - } - - template - void matrix::fill_leading_diagonal(const T& item) throw() - { - for (unsigned i = 0; i < m_cols && i < m_rows; i++) - insert(i, i, item); - } - - template - void matrix::fill_trailing_diagonal(const T& item) throw() - { - for (unsigned i = 0; i < m_cols && i < m_rows; i++) - insert(i, m_cols-i-1, item); - } - - template - void matrix::make_identity(const T& one, const T& zero) throw() - { - fill(zero); - fill_leading_diagonal(one); - } - - template - void matrix::transpose(void) throw() - { - // no gain in manipulating this, since building a new matrix is no less efficient - matrix transposed(columns(), rows()); - for (unsigned row = 0; row < rows(); row++) - for (unsigned col = 0; col < columns(); col++) - transposed.insert(col,row,item(row,col)); - // TODO - avoid an extra copy by swapping the member data here - *this = transposed; - } - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + + template + matrix::matrix(unsigned rows, unsigned cols, const T& fill) throw() + { + m_rows = 0; + m_cols = 0; + m_data = 0; + resize(rows,cols,fill); + } + + template + matrix::~matrix(void) throw() + { + for (unsigned row = 0; row < m_rows; row++) + delete[] m_data[row]; + delete[] m_data; + } + + template + matrix::matrix(const matrix& r) throw() + { + m_rows = 0; + m_cols = 0; + m_data = 0; + *this = r; + } + + template + matrix& matrix::operator =(const matrix& right) throw() + { + // clear the old values + for (unsigned row = 0; row < m_rows; row++) + delete[] m_data[row]; + delete[] m_data; + m_rows = 0; + m_cols = 0; + m_data = 0; + // now reconstruct with the new + resize(right.m_rows, right.m_cols); + for (unsigned row = 0; row < m_rows; row++) + for (unsigned col = 0; col < m_cols; col++) + m_data[row][col] = right.m_data[row][col]; + return *this; + } + + template + void matrix::resize(unsigned rows, unsigned cols, const T& fill) throw() + { + // a grid is an array of rows, where each row is an array of T + // a zero-row or zero-column matrix has a null grid + // TODO - make this exception-safe - new could throw here and that would cause a memory leak + T** new_grid = 0; + if (rows && cols) + { + new_grid = new T*[rows]; + for (unsigned row = 0; row < rows; row++) + { + new_grid[row] = new T[cols]; + // copy old items to the new grid but only within the bounds of the intersection of the old and new grids + // fill the rest of the grid with the initial value + for (unsigned col = 0; col < cols; col++) + if (row < m_rows && col < m_cols) + new_grid[row][col] = m_data[row][col]; + else + new_grid[row][col] = fill; + } + } + // destroy the old grid + for (unsigned row = 0; row < m_rows; row++) + delete[] m_data[row]; + delete[] m_data; + // move the new data into the matrix + m_data = new_grid; + m_rows = rows; + m_cols = cols; + } + + template + unsigned matrix::rows(void) const throw() + { + return m_rows; + } + + template + unsigned matrix::columns(void) const throw() + { + return m_cols; + } + + template + void matrix::erase(const T& fill) throw() + { + for (unsigned row = 0; row < m_rows; row++) + for (unsigned col = 0; col < m_cols; col++) + insert(row,col,fill); + } + + template + void matrix::erase(unsigned row, unsigned col, const T& fill) throw(std::out_of_range) + { + insert(row,col,fill); + } + + template + void matrix::insert(unsigned row, unsigned col, const T& element) throw(std::out_of_range) + { + if (row >= m_rows) throw std::out_of_range("matrix::insert row"); + if (col >= m_cols) throw std::out_of_range("matrix::insert col"); + m_data[row][col] = element; + } + + template + const T& matrix::item(unsigned row, unsigned col) const throw(std::out_of_range) + { + if (row >= m_rows) throw std::out_of_range("matrix::item row"); + if (col >= m_cols) throw std::out_of_range("matrix::item col"); + return m_data[row][col]; + } + + template + T& matrix::item(unsigned row, unsigned col) throw(std::out_of_range) + { + if (row >= m_rows) throw std::out_of_range("matrix::item row"); + if (col >= m_cols) throw std::out_of_range("matrix::item col"); + return m_data[row][col]; + } + + template + const T& matrix::operator()(unsigned row, unsigned col) const throw(std::out_of_range) + { + if (row >= m_rows) throw std::out_of_range("matrix::operator() row"); + if (col >= m_cols) throw std::out_of_range("matrix::operator() col"); + return m_data[row][col]; + } + + template + T& matrix::operator()(unsigned row, unsigned col) throw(std::out_of_range) + { + if (row >= m_rows) throw std::out_of_range("matrix::operator() row"); + if (col >= m_cols) throw std::out_of_range("matrix::operator() col"); + return m_data[row][col]; + } + + template + void matrix::fill(const T& item) throw() + { + erase(item); + } + + template + void matrix::fill_column(unsigned col, const T& item) throw (std::out_of_range) + { + if (col >= m_cols) throw std::out_of_range("matrix::fill_column"); + for (unsigned row = 0; row < m_rows; row++) + insert(row, col, item); + } + + template + void matrix::fill_row(unsigned row, const T& item) throw (std::out_of_range) + { + if (row >= m_rows) throw std::out_of_range("matrix::fill_row"); + for (unsigned col = 0; col < m_cols; col++) + insert(row, col, item); + } + + template + void matrix::fill_leading_diagonal(const T& item) throw() + { + for (unsigned i = 0; i < m_cols && i < m_rows; i++) + insert(i, i, item); + } + + template + void matrix::fill_trailing_diagonal(const T& item) throw() + { + for (unsigned i = 0; i < m_cols && i < m_rows; i++) + insert(i, m_cols-i-1, item); + } + + template + void matrix::make_identity(const T& one, const T& zero) throw() + { + fill(zero); + fill_leading_diagonal(one); + } + + template + void matrix::transpose(void) throw() + { + // no gain in manipulating this, since building a new matrix is no less efficient + matrix transposed(columns(), rows()); + for (unsigned row = 0; row < rows(); row++) + for (unsigned col = 0; col < columns(); col++) + transposed.insert(col,row,item(row,col)); + // TODO - avoid an extra copy by swapping the member data here + *this = transposed; + } + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + diff --git a/src/stlplus/containers/ntree.hpp b/src/stlplus/containers/ntree.hpp index 1dd41c0..0299656 100644 --- a/src/stlplus/containers/ntree.hpp +++ b/src/stlplus/containers/ntree.hpp @@ -1,364 +1,399 @@ -#ifndef STLPLUS_NTREE -#define STLPLUS_NTREE -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// A templated n-ary tree data structure. STL-like but the definition of -// iterators is really only applicable to one-dimensional structures. I use -// iterators to access tree nodes, but there is no increment or decrement -// operators for them. I also define prefix and postfix traversal iterators -// which do have increment. - -//////////////////////////////////////////////////////////////////////////////// -#include "containers_fixes.hpp" -#include "exceptions.hpp" -#include "safe_iterator.hpp" - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // Internals - - template class ntree_node; - template class ntree; - template class ntree_iterator; - template class ntree_prefix_iterator; - template class ntree_postfix_iterator; - - //////////////////////////////////////////////////////////////////////////////// - // Iterators - - // Simple iterators which are just used as pointers to tree nodes. These have - // no increment or decrement operations defined. An uninitialised iterator is - // null - similarly, if you ask for the root of an empty tree or the parent of - // the root node then you get a null iterator. - - template - class ntree_iterator : public safe_iterator,ntree_node > - { - public: - // local type definitions - // an iterator points to an object whilst a const_iterator points to a const object - typedef ntree_iterator iterator; - typedef ntree_iterator const_iterator; - typedef ntree_iterator this_iterator; - typedef TRef reference; - typedef TPtr pointer; - - // constructor to create a null iterator - you must assign a valid value to this iterator before using it - ntree_iterator(void); - ~ntree_iterator(void); - - // Type conversion methods allow const_iterator and iterator to be converted - const_iterator constify(void) const; - iterator deconstify(void) const; - - // tests useful for putting iterators into other STL structures and for testing whether iteration has completed - bool operator == (const this_iterator& r) const; - bool operator != (const this_iterator& r) const; - bool operator < (const this_iterator& r) const; - - // access the node data - a const_iterator gives you a const element, an iterator a non-const element - // it is illegal to dereference an invalid (i.e. null or end) iterator - reference operator*(void) const - throw(null_dereference,end_dereference); - pointer operator->(void) const - throw(null_dereference,end_dereference); - - friend class ntree; - friend class ntree_prefix_iterator; - friend class ntree_postfix_iterator; - - public: - // Note: I had to make this public to get round a compiler problem - it should be private - // you cannot create a valid iterator except by calling an ntree method that returns one - // constructor used by ntree to create a non-null iterator - explicit ntree_iterator(ntree_node* node); - // constructor used by ntree to create an end iterator - explicit ntree_iterator(const ntree* owner); - // used to create an alias of an iterator - explicit ntree_iterator(const safe_iterator, ntree_node >& iterator); - }; - - // Traversal iterators are like iterators but they have increment operators (++) - // - prefix_iterator visits the nodes of the tree in prefix order - // - postfix_iterator visits the nodes of the tree in postfix order. - // There is no such thing as infix order for an n-ary tree and you cannot - // traverse backwards with these iterators. These follow the STL convention in - // that you iterate from a begin to an end - in this case ntree exports - // prefix_begin()/prefix_end() and postfix_begin()/postfix_end(). You can - // simplify these iterators to the basic iterator above for functions that - // require a simple iterator. - - template - class ntree_prefix_iterator - { - public: - typedef ntree_prefix_iterator iterator; - typedef ntree_prefix_iterator const_iterator; - typedef ntree_prefix_iterator this_iterator; - typedef ntree_iterator simple_iterator; - typedef TRef reference; - typedef TPtr pointer; - - // constructor to create a null iterator - you must assign a valid value to this iterator before using it - ntree_prefix_iterator(void); - ~ntree_prefix_iterator(void); - - // tests - // a null iterator is one that has not been initialised with a value yet - // i.e. you just declared it but didn't assign to it - bool null(void) const; - // an end iterator is one that points to the end element of the list of nodes - // in STL conventions this is one past the last valid element and must not be dereferenced - bool end(void) const; - // a valid iterator is one that can be dereferenced - // i.e. non-null and non-end - bool valid(void) const; - - // Type conversion methods allow const_iterator and iterator to be converted - // convert an iterator/const_iterator to a const_iterator - const_iterator constify(void) const; - iterator deconstify(void) const; - - // generate a simple iterator from a traversal iterator - ntree_iterator simplify(void) const; - - // tests useful for putting iterators into other STL structures and for testing whether iteration has completed - bool operator == (const this_iterator& r) const; - bool operator != (const this_iterator& r) const; - bool operator < (const this_iterator& r) const; - - // increment/decrement operators used to step through the set of all nodes in a graph - // it is only legal to increment a valid iterator - // pre-increment - this_iterator& operator ++ (void) - throw(null_dereference,end_dereference); - // post-increment - this_iterator operator ++ (int) - throw(null_dereference,end_dereference); - - // access the node data - a const_iterator gives you a const element, an iterator a non-const element - // it is illegal to dereference an invalid (i.e. null or end) iterator - reference operator*(void) const - throw(null_dereference,end_dereference); - pointer operator->(void) const - throw(null_dereference,end_dereference); - - friend class ntree; - friend class ntree_iterator; - - private: - ntree_iterator m_iterator; - - explicit ntree_prefix_iterator(const ntree_iterator& i); - const ntree_iterator& get_iterator(void) const; - ntree_iterator& get_iterator(void); - }; - - //////////////////////////////////////////////////////////////////////////////// - - template - class ntree_postfix_iterator - { - public: - typedef ntree_postfix_iterator iterator; - typedef ntree_postfix_iterator const_iterator; - typedef ntree_postfix_iterator this_iterator; - typedef ntree_iterator simple_iterator; - typedef TRef reference; - typedef TPtr pointer; - - // constructor to create a null iterator - you must assign a valid value to this iterator before using it - ntree_postfix_iterator(void); - ~ntree_postfix_iterator(void); - - // tests - // a null iterator is one that has not been initialised with a value yet - // i.e. you just declared it but didn't assign to it - bool null(void) const; - // an end iterator is one that points to the end element of the list of nodes - // in STL conventions this is one past the last valid element and must not be dereferenced - bool end(void) const; - // a valid iterator is one that can be dereferenced - // i.e. non-null and non-end - bool valid(void) const; - - // Type conversion methods allow const_iterator and iterator to be converted - // convert an iterator/const_iterator to a const_iterator - const_iterator constify(void) const; - iterator deconstify(void) const; - - // generate a simple iterator from a traversal iterator - ntree_iterator simplify(void) const; - - // tests useful for putting iterators into other STL structures and for testing whether iteration has completed - bool operator == (const this_iterator& r) const; - bool operator != (const this_iterator& r) const; - bool operator < (const this_iterator& r) const; - - // increment/decrement operators used to step through the set of all nodes in a graph - // it is only legal to increment a valid iterator - // pre-increment - this_iterator& operator ++ (void) - throw(null_dereference,end_dereference); - // post-increment - this_iterator operator ++ (int) - throw(null_dereference,end_dereference); - - // access the node data - a const_iterator gives you a const element, an iterator a non-const element - // it is illegal to dereference an invalid (i.e. null or end) iterator - reference operator*(void) const - throw(null_dereference,end_dereference); - pointer operator->(void) const - throw(null_dereference,end_dereference); - - friend class ntree; - friend class ntree_iterator; - - private: - ntree_iterator m_iterator; - - explicit ntree_postfix_iterator(const ntree_iterator& i); - const ntree_iterator& get_iterator(void) const; - ntree_iterator& get_iterator(void); - }; - - //////////////////////////////////////////////////////////////////////////////// - // The Ntree class - //////////////////////////////////////////////////////////////////////////////// - - template - class ntree - { - public: - // STL-like typedefs for the types and iterators - typedef T value_type; - - typedef ntree_iterator iterator; - typedef ntree_iterator const_iterator; - - typedef ntree_prefix_iterator prefix_iterator; - typedef ntree_prefix_iterator const_prefix_iterator; - - typedef ntree_postfix_iterator postfix_iterator; - typedef ntree_postfix_iterator const_postfix_iterator; - - ////////////////////////////////////////////////////////////////////////////// - // Constructors, destructors and copies - - ntree(void); - ~ntree(void); - - // copy constructor and assignment both copy the tree - ntree(const ntree&); - ntree& operator=(const ntree&); - - ////////////////////////////////////////////////////////////////////////////// - // size tests - - // tests on whole tree - bool empty(void) const; - unsigned size(void) const; - - // tests for number of nodes in subtree starting at node - unsigned size(const const_iterator& node) const - throw(wrong_object,null_dereference,end_dereference); - unsigned size(const iterator& node) - throw(wrong_object,null_dereference,end_dereference); - - // test for depth of tree from root to node - unsigned depth(const const_iterator& node) const - throw(wrong_object,null_dereference,end_dereference); - unsigned depth(const iterator& node) - throw(wrong_object,null_dereference,end_dereference); - - ////////////////////////////////////////////////////////////////////////////// - // direct traversal - - const_iterator root(void) const; - iterator root(void); - - unsigned children(const const_iterator& node) const - throw(wrong_object,null_dereference,end_dereference); - unsigned children(const iterator& node) - throw(wrong_object,null_dereference,end_dereference); - - const_iterator child(const const_iterator& node, unsigned child) const - throw(wrong_object,null_dereference,end_dereference,std::out_of_range); - iterator child(const iterator& node, unsigned child) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range); - - const_iterator parent(const const_iterator& node) const - throw(wrong_object,null_dereference,end_dereference); - iterator parent(const iterator& node) - throw(wrong_object,null_dereference,end_dereference); - - ////////////////////////////////////////////////////////////////////////////// - // iterator traversal - - const_prefix_iterator prefix_begin(void) const; - prefix_iterator prefix_begin(void); - const_prefix_iterator prefix_end(void) const; - prefix_iterator prefix_end(void); - - const_postfix_iterator postfix_begin(void) const; - postfix_iterator postfix_begin(void); - const_postfix_iterator postfix_end(void) const; - postfix_iterator postfix_end(void); - - ////////////////////////////////////////////////////////////////////////////// - // modification - - iterator insert(const T&); - - iterator insert(const iterator& node, unsigned child, const T&) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range); - iterator append(const iterator& node, const T&) - throw(wrong_object,null_dereference,end_dereference); - - iterator insert(const iterator& node, unsigned child, const ntree&) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range); - iterator append(const iterator& node, const ntree&) - throw(wrong_object,null_dereference,end_dereference); - - iterator push(const iterator& node, const T&) - throw(wrong_object,null_dereference,end_dereference); - void pop(const iterator& node, unsigned child) - throw(wrong_object,null_dereference,end_dereference); - - void erase(void); - void erase(const iterator& node) - throw(wrong_object,null_dereference,end_dereference); - void erase(const iterator& node, unsigned child) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range); - - ntree subtree(void); - ntree subtree(const iterator& node) - throw(wrong_object,null_dereference,end_dereference); - ntree subtree(const iterator& node, unsigned child) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range); - - ntree cut(void); - ntree cut(const iterator& node) - throw(wrong_object,null_dereference,end_dereference); - ntree cut(const iterator& node, unsigned child) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range); - - ////////////////////////////////////////////////////////////////////////////// - - private: - ntree_node* m_root; - }; - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#include "ntree.tpp" -#endif +#ifndef STLPLUS_NTREE +#define STLPLUS_NTREE +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// A templated n-ary tree data structure. STL-like but the definition of +// iterators is really only applicable to one-dimensional structures. I use +// iterators to access tree nodes, but there is no increment or decrement +// operators for them. I also define prefix and postfix traversal iterators +// which do have increment. + +//////////////////////////////////////////////////////////////////////////////// +#include "containers_fixes.hpp" +#include "exceptions.hpp" +#include "safe_iterator.hpp" + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // Internals + + template class ntree_node; + template class ntree; + template class ntree_iterator; + template class ntree_prefix_iterator; + template class ntree_postfix_iterator; + + //////////////////////////////////////////////////////////////////////////////// + // Iterators + + // Simple iterators which are just used as pointers to tree nodes. These have + // no increment or decrement operations defined. An uninitialised iterator is + // null - similarly, if you ask for the root of an empty tree or the parent of + // the root node then you get a null iterator. + + template + class ntree_iterator : public safe_iterator,ntree_node > + { + public: + // local type definitions + // an iterator points to an object whilst a const_iterator points to a const object + typedef ntree_iterator iterator; + typedef ntree_iterator const_iterator; + typedef ntree_iterator this_iterator; + typedef TRef reference; + typedef TPtr pointer; + + // constructor to create a null iterator - you must assign a valid value to this iterator before using it + ntree_iterator(void); + ~ntree_iterator(void); + + // Type conversion methods allow const_iterator and iterator to be converted + const_iterator constify(void) const; + iterator deconstify(void) const; + + // tests useful for putting iterators into other STL structures and for testing whether iteration has completed + bool operator == (const this_iterator& r) const; + bool operator != (const this_iterator& r) const; + bool operator < (const this_iterator& r) const; + + // access the node data - a const_iterator gives you a const element, an iterator a non-const element + // it is illegal to dereference an invalid (i.e. null or end) iterator + reference operator*(void) const + throw(null_dereference,end_dereference); + pointer operator->(void) const + throw(null_dereference,end_dereference); + + friend class ntree; + friend class ntree_prefix_iterator; + friend class ntree_postfix_iterator; + + public: + // Note: I had to make this public to get round a problem implementing persistence - it should be private + // you cannot create a valid iterator except by calling an ntree method that returns one + // constructor used by ntree to create a non-null iterator + explicit ntree_iterator(ntree_node* node); + // constructor used by ntree to create an end iterator + explicit ntree_iterator(const ntree* owner); + // used to create an alias of an iterator + explicit ntree_iterator(const safe_iterator, ntree_node >& iterator); + }; + + // Traversal iterators are like iterators but they have increment operators (++) + // - prefix_iterator visits the nodes of the tree in prefix order + // - postfix_iterator visits the nodes of the tree in postfix order. + // There is no such thing as infix order for an n-ary tree and you cannot + // traverse backwards with these iterators. These follow the STL convention in + // that you iterate from a begin to an end - in this case ntree exports + // prefix_begin()/prefix_end() and postfix_begin()/postfix_end(). You can + // simplify these iterators to the basic iterator above for functions that + // require a simple iterator. + + template + class ntree_prefix_iterator + { + public: + typedef ntree_prefix_iterator iterator; + typedef ntree_prefix_iterator const_iterator; + typedef ntree_prefix_iterator this_iterator; + typedef ntree_iterator simple_iterator; + typedef TRef reference; + typedef TPtr pointer; + + // constructor to create a null iterator - you must assign a valid value to this iterator before using it + ntree_prefix_iterator(void); + ~ntree_prefix_iterator(void); + + // tests + // a null iterator is one that has not been initialised with a value yet + // i.e. you just declared it but didn't assign to it + bool null(void) const; + // an end iterator is one that points to the end element of the list of nodes + // in STL conventions this is one past the last valid element and must not be dereferenced + bool end(void) const; + // a valid iterator is one that can be dereferenced + // i.e. non-null and non-end + bool valid(void) const; + + // Type conversion methods allow const_iterator and iterator to be converted + // convert an iterator/const_iterator to a const_iterator + const_iterator constify(void) const; + iterator deconstify(void) const; + + // generate a simple iterator from a traversal iterator + ntree_iterator simplify(void) const; + + // tests useful for putting iterators into other STL structures and for testing whether iteration has completed + bool operator == (const this_iterator& r) const; + bool operator != (const this_iterator& r) const; + bool operator < (const this_iterator& r) const; + + // increment/decrement operators used to step through the set of all nodes in a graph + // it is only legal to increment a valid iterator + // pre-increment + this_iterator& operator ++ (void) + throw(null_dereference,end_dereference); + // post-increment + this_iterator operator ++ (int) + throw(null_dereference,end_dereference); + + // access the node data - a const_iterator gives you a const element, an iterator a non-const element + // it is illegal to dereference an invalid (i.e. null or end) iterator + reference operator*(void) const + throw(null_dereference,end_dereference); + pointer operator->(void) const + throw(null_dereference,end_dereference); + + friend class ntree; + friend class ntree_iterator; + + private: + ntree_iterator m_iterator; + + explicit ntree_prefix_iterator(const ntree_iterator& i); + const ntree_iterator& get_iterator(void) const; + ntree_iterator& get_iterator(void); + }; + + //////////////////////////////////////////////////////////////////////////////// + + template + class ntree_postfix_iterator + { + public: + typedef ntree_postfix_iterator iterator; + typedef ntree_postfix_iterator const_iterator; + typedef ntree_postfix_iterator this_iterator; + typedef ntree_iterator simple_iterator; + typedef TRef reference; + typedef TPtr pointer; + + // constructor to create a null iterator - you must assign a valid value to this iterator before using it + ntree_postfix_iterator(void); + ~ntree_postfix_iterator(void); + + // tests + // a null iterator is one that has not been initialised with a value yet + // i.e. you just declared it but didn't assign to it + bool null(void) const; + // an end iterator is one that points to the end element of the list of nodes + // in STL conventions this is one past the last valid element and must not be dereferenced + bool end(void) const; + // a valid iterator is one that can be dereferenced + // i.e. non-null and non-end + bool valid(void) const; + + // Type conversion methods allow const_iterator and iterator to be converted + // convert an iterator/const_iterator to a const_iterator + const_iterator constify(void) const; + iterator deconstify(void) const; + + // generate a simple iterator from a traversal iterator + ntree_iterator simplify(void) const; + + // tests useful for putting iterators into other STL structures and for testing whether iteration has completed + bool operator == (const this_iterator& r) const; + bool operator != (const this_iterator& r) const; + bool operator < (const this_iterator& r) const; + + // increment/decrement operators used to step through the set of all nodes in a graph + // it is only legal to increment a valid iterator + // pre-increment + this_iterator& operator ++ (void) + throw(null_dereference,end_dereference); + // post-increment + this_iterator operator ++ (int) + throw(null_dereference,end_dereference); + + // access the node data - a const_iterator gives you a const element, an iterator a non-const element + // it is illegal to dereference an invalid (i.e. null or end) iterator + reference operator*(void) const + throw(null_dereference,end_dereference); + pointer operator->(void) const + throw(null_dereference,end_dereference); + + friend class ntree; + friend class ntree_iterator; + + private: + ntree_iterator m_iterator; + + explicit ntree_postfix_iterator(const ntree_iterator& i); + const ntree_iterator& get_iterator(void) const; + ntree_iterator& get_iterator(void); + }; + + //////////////////////////////////////////////////////////////////////////////// + // The Ntree class + //////////////////////////////////////////////////////////////////////////////// + + template + class ntree + { + public: + // STL-like typedefs for the types and iterators + typedef T value_type; + + typedef ntree_iterator iterator; + typedef ntree_iterator const_iterator; + + typedef ntree_prefix_iterator prefix_iterator; + typedef ntree_prefix_iterator const_prefix_iterator; + + typedef ntree_postfix_iterator postfix_iterator; + typedef ntree_postfix_iterator const_postfix_iterator; + + ////////////////////////////////////////////////////////////////////////////// + // Constructors, destructors and copies + + ntree(void); + ~ntree(void); + + // copy constructor and assignment both copy the tree + ntree(const ntree&); + ntree& operator=(const ntree&); + + ////////////////////////////////////////////////////////////////////////////// + // size tests + + // tests on whole tree + bool empty(void) const; + unsigned size(void) const; + + // tests for number of nodes in subtree starting at node + unsigned size(const const_iterator& node) const + throw(wrong_object,null_dereference,end_dereference); + unsigned size(const iterator& node) + throw(wrong_object,null_dereference,end_dereference); + + // test for depth of tree from root to node + unsigned depth(const const_iterator& node) const + throw(wrong_object,null_dereference,end_dereference); + unsigned depth(const iterator& node) + throw(wrong_object,null_dereference,end_dereference); + + ////////////////////////////////////////////////////////////////////////////// + // direct traversal + + const_iterator root(void) const; + iterator root(void); + + unsigned children(const const_iterator& node) const + throw(wrong_object,null_dereference,end_dereference); + unsigned children(const iterator& node) + throw(wrong_object,null_dereference,end_dereference); + + const_iterator child(const const_iterator& node, unsigned child) const + throw(wrong_object,null_dereference,end_dereference,std::out_of_range); + iterator child(const iterator& node, unsigned child) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range); + + const_iterator parent(const const_iterator& node) const + throw(wrong_object,null_dereference,end_dereference); + iterator parent(const iterator& node) + throw(wrong_object,null_dereference,end_dereference); + + ////////////////////////////////////////////////////////////////////////////// + // iterator traversal + + const_prefix_iterator prefix_begin(void) const; + prefix_iterator prefix_begin(void); + const_prefix_iterator prefix_end(void) const; + prefix_iterator prefix_end(void); + + const_postfix_iterator postfix_begin(void) const; + postfix_iterator postfix_begin(void); + const_postfix_iterator postfix_end(void) const; + postfix_iterator postfix_end(void); + + ////////////////////////////////////////////////////////////////////////////// + // modification + + // discard previous contents and create a new root node + iterator insert(const T&); + // add a new child inserted into the node's children at the specified place + iterator insert(const iterator& node, unsigned child, const T&) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range); + // shortcut for insert at the end i.e. tree.insert(node, node.children(), value) + iterator insert(const iterator& node, const T&) + throw(wrong_object,null_dereference,end_dereference); + // old name for the above + iterator append(const iterator& node, const T&) + throw(wrong_object,null_dereference,end_dereference); + + // discard previous contents and copy the tree + iterator insert(const ntree&); + // add a copy of the tree as a new child inserted into the node's children at the specified place + iterator insert(const iterator& node, unsigned child, const ntree&) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range); + // shortcut for insert at the end i.e. tree.insert(node, node.children(), value) + iterator insert(const iterator& node, const ntree&) + throw(wrong_object,null_dereference,end_dereference); + // old name for the above + iterator append(const iterator& node, const ntree&) + throw(wrong_object,null_dereference,end_dereference); + + // discard previous contents and move the tree without copying + // invalidates all iterators to the old tree + iterator move(ntree&); + // move the tree to become the designated child + // invalidates all iterators to the old tree + iterator move(const iterator& node, unsigned child, ntree&) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range); + // shortcut for move to the last child i.e. node.move(node, node.children(), value) + iterator move(const iterator& node, ntree&) + throw(wrong_object,null_dereference,end_dereference); + + // replace the node with the new value, pushing the old node down to make it the child + // returns the iterator to the new, pushed node + iterator push(const iterator& node, const T&) + throw(wrong_object,null_dereference,end_dereference); + // erases the specified child, moving its children up to become the node's children + void pop(const iterator& node, unsigned child) + throw(wrong_object,null_dereference,end_dereference); + + // erase the whole tree + void erase(void); + // erase the node and all its children + void erase(const iterator& node) + throw(wrong_object,null_dereference,end_dereference); + // erase the specified child + void erase(const iterator& node, unsigned child) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range); + + // get a copy of the tree as a tree + ntree subtree(void); + // get a copy of the subtree as a tree with the specified node as root + ntree subtree(const iterator& node) + throw(wrong_object,null_dereference,end_dereference); + // get a copy of the subtree as a tree with the specified child as root + ntree subtree(const iterator& node, unsigned child) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range); + + // move the whole tree to make a new tree + ntree cut(void); + // move the subtree to make a new tree with the specified node as root + ntree cut(const iterator& node) + throw(wrong_object,null_dereference,end_dereference); + // move the subtree to make a new tree with the specified child as root + ntree cut(const iterator& node, unsigned child) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range); + + ////////////////////////////////////////////////////////////////////////////// + + private: + ntree_node* m_root; + }; + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#include "ntree.tpp" +#endif diff --git a/src/stlplus/containers/ntree.tpp b/src/stlplus/containers/ntree.tpp index 1467aa8..9236327 100644 --- a/src/stlplus/containers/ntree.tpp +++ b/src/stlplus/containers/ntree.tpp @@ -1,913 +1,962 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// -#include -#include - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // ntree_node - - template - class ntree_node - { - public: - master_iterator, ntree_node > m_master; - T m_data; - ntree_node* m_parent; - std::vector*> m_children; - - public: - ntree_node(const ntree* owner, const T& data = T()) : - m_master(owner,this), m_data(data), m_parent(0) - { - } - - void change_owner(const ntree* owner) - { - m_master.change_owner(owner); - for (TYPENAME std::vector*>::iterator i = m_children.begin(); i != m_children.end(); i++) - (*i)->change_owner(owner); - } - - ~ntree_node(void) - { - m_parent = 0; - for (TYPENAME std::vector*>::iterator i = m_children.begin(); i != m_children.end(); i++) - delete *i; - } - - }; - - template - static ntree_node* ntree_copy(const ntree* new_owner, ntree_node* root) - { - if (!root) return 0; - ntree_node* new_tree = new ntree_node(new_owner, root->m_data); - for (TYPENAME std::vector*>::iterator i = root->m_children.begin(); i != root->m_children.end(); i++) - { - ntree_node* new_child = ntree_copy(new_owner, *i); - new_tree->m_children.push_back(new_child); - new_child->m_parent = new_tree; - } - return new_tree; - } - - template - static unsigned ntree_size(ntree_node* root) - { - if (!root) return 0; - unsigned result = 1; - for (TYPENAME std::vector*>::iterator i = root->m_children.begin(); i != root->m_children.end(); i++) - result += ntree_size(*i); - return result; - } - - template - static unsigned ntree_depth(ntree_node* root) - { - unsigned depth = 0; - for (ntree_node* i = root; i; i = i->m_parent) - depth++; - return depth; - } - - //////////////////////////////////////////////////////////////////////////////// - // ntree_iterator - - // constructor to create a null iterator - you must assign a valid value to this iterator before using it - template - ntree_iterator::ntree_iterator(void) - { - } - - // used to create an alias of an iterator - template - ntree_iterator::ntree_iterator(const safe_iterator, ntree_node >& iterator) : - safe_iterator,ntree_node >(iterator) - { - } - - // constructor used by ntree to create a non-null iterator - template - ntree_iterator::ntree_iterator(ntree_node* node) : - safe_iterator,ntree_node >(node->m_master) - { - } - - // constructor used by ntree to create an end iterator - template - ntree_iterator::ntree_iterator(const ntree* owner) : - safe_iterator,ntree_node >(owner) - { - } - - // destructor - template - ntree_iterator::~ntree_iterator(void) - { - } - - template - TYPENAME ntree_iterator::const_iterator ntree_iterator::constify(void) const - { - return ntree_iterator(*this); - } - - template - TYPENAME ntree_iterator::iterator ntree_iterator::deconstify(void) const - { - return ntree_iterator(*this); - } - - template - bool ntree_iterator::operator == (const TYPENAME ntree_iterator::this_iterator& r) const - { - return equal(r); - } - - template - bool ntree_iterator::operator != (const TYPENAME ntree_iterator::this_iterator& r) const - { - return !operator==(r); - } - - template - bool ntree_iterator::operator < (const TYPENAME ntree_iterator::this_iterator& r) const - { - return compare(r) < 0; - } - - template - TYPENAME ntree_iterator::reference ntree_iterator::operator*(void) const - throw(null_dereference,end_dereference) - { - this->assert_valid(); - return this->node()->m_data; - } - - template - TYPENAME ntree_iterator::pointer ntree_iterator::operator->(void) const - throw(null_dereference,end_dereference) - { - return &(operator*()); - } - - //////////////////////////////////////////////////////////////////////////////// - // ntree_prefix_iterator - - template - ntree_prefix_iterator::ntree_prefix_iterator(void) - { - } - - template - ntree_prefix_iterator::~ntree_prefix_iterator(void) - { - } - - template - ntree_prefix_iterator::ntree_prefix_iterator(const ntree_iterator& i) : - m_iterator(i) - { - // this is initialised with the root node - // which is also the first node in prefix traversal order - } - - template - bool ntree_prefix_iterator::null(void) const - { - return m_iterator.null(); - } - - template - bool ntree_prefix_iterator::end(void) const - { - return m_iterator.end(); - } - - template - bool ntree_prefix_iterator::valid(void) const - { - return m_iterator.valid(); - } - - template - TYPENAME ntree_prefix_iterator::const_iterator ntree_prefix_iterator::constify(void) const - { - return ntree_prefix_iterator(m_iterator); - } - - template - TYPENAME ntree_prefix_iterator::iterator ntree_prefix_iterator::deconstify(void) const - { - return ntree_prefix_iterator(m_iterator); - } - - template - ntree_iterator ntree_prefix_iterator::simplify(void) const - { - return m_iterator; - } - - template - bool ntree_prefix_iterator::operator == (const TYPENAME ntree_prefix_iterator::this_iterator& r) const - { - return m_iterator == r.m_iterator; - } - - template - bool ntree_prefix_iterator::operator != (const TYPENAME ntree_prefix_iterator::this_iterator& r) const - { - return m_iterator != r.m_iterator; - } - - template - bool ntree_prefix_iterator::operator < (const TYPENAME ntree_prefix_iterator::this_iterator& r) const - { - return m_iterator < r.m_iterator; - } - - template - TYPENAME ntree_prefix_iterator::this_iterator& ntree_prefix_iterator::operator ++ (void) - throw(null_dereference,end_dereference) - { - // pre-increment operator - // algorithm: if there are any children, visit child 0, otherwise, go to - // parent and deduce which child the start node was of that parent - if - // there are further children, go into the next one. Otherwise, go up the - // tree and test again for further children. Return null if there are no - // further nodes - m_iterator.assert_valid(); - ntree_node* old_node = m_iterator.node(); - if (!old_node->m_children.empty()) - { - // simply take the first child of this node - m_iterator.set(old_node->m_children[0]->m_master); - } - else - { - // this loop walks up the parent pointers - // either it will walk off the top and exit or a new node will be found and the loop will exit - for (;;) - { - // go up a level - ntree_node* parent = old_node->m_parent; - if (!parent) - { - // we've walked off the top of the tree, so return end - m_iterator.set_end(); - break; - } - else - { - // otherwise walk down the next child - if there is one - // find which index the old node was relative to this node - TYPENAME std::vector*>::iterator found = - std::find(parent->m_children.begin(), parent->m_children.end(), old_node); - // if this was found, then see if there is another and if so return that - found++; - if (found != parent->m_children.end()) - { - // visit the next child - m_iterator.set((*found)->m_master); - break; - } - else - { - // keep going up - old_node = parent; - } - } - } - } - return *this; - } - - template - TYPENAME ntree_prefix_iterator::this_iterator ntree_prefix_iterator::operator ++ (int) - throw(null_dereference,end_dereference) - { - // post-increment is defined in terms of the pre-increment - ntree_prefix_iterator result(*this); - ++(*this); - return result; - } - - template - TYPENAME ntree_prefix_iterator::reference ntree_prefix_iterator::operator*(void) const - throw(null_dereference,end_dereference) - { - return m_iterator.operator*(); - } - - template - TYPENAME ntree_prefix_iterator::pointer ntree_prefix_iterator::operator->(void) const - throw(null_dereference,end_dereference) - { - return m_iterator.operator->(); - } - - template - const ntree_iterator& ntree_prefix_iterator::get_iterator(void) const - { - return m_iterator; - } - - template - ntree_iterator& ntree_prefix_iterator::get_iterator(void) - { - return m_iterator; - } - - //////////////////////////////////////////////////////////////////////////////// - // ntree_postfix_iterator - - template - ntree_postfix_iterator::ntree_postfix_iterator(void) - { - } - - template - ntree_postfix_iterator::~ntree_postfix_iterator(void) - { - } - - template - ntree_postfix_iterator::ntree_postfix_iterator(const ntree_iterator& i) : - m_iterator(i) - { - // this is initialised with the root node - // initially traverse to the first node to be visited - if (m_iterator.valid()) - { - ntree_node* node = m_iterator.node(); - while (!node->m_children.empty()) - node = node->m_children[0]; - m_iterator.set(node->m_master); - } - } - - template - bool ntree_postfix_iterator::null(void) const - { - return m_iterator.null(); - } - - template - bool ntree_postfix_iterator::end(void) const - { - return m_iterator.end(); - } - - template - bool ntree_postfix_iterator::valid(void) const - { - return m_iterator.valid(); - } - - template - TYPENAME ntree_postfix_iterator::const_iterator ntree_postfix_iterator::constify(void) const - { - return ntree_postfix_iterator(m_iterator); - } - - template - TYPENAME ntree_postfix_iterator::iterator ntree_postfix_iterator::deconstify(void) const - { - return ntree_postfix_iterator(m_iterator); - } - - template - ntree_iterator ntree_postfix_iterator::simplify(void) const - { - return m_iterator; - } - - template - bool ntree_postfix_iterator::operator == (const TYPENAME ntree_postfix_iterator::this_iterator& r) const - { - return m_iterator == r.m_iterator; - } - - template - bool ntree_postfix_iterator::operator != (const TYPENAME ntree_postfix_iterator::this_iterator& r) const - { - return m_iterator != r.m_iterator; - } - - template - bool ntree_postfix_iterator::operator < (const TYPENAME ntree_postfix_iterator::this_iterator& r) const - { - return m_iterator < r.m_iterator; - } - - template - TYPENAME ntree_postfix_iterator::this_iterator& ntree_postfix_iterator::operator ++ (void) - throw(null_dereference,end_dereference) - { - // pre-increment operator - // algorithm: this node has been visited, therefore all children must have - // already been visited. So go to parent. Return null if the parent is null. - // Otherwise deduce which child the start node was of that parent - if there - // are further children, go into the next one and then walk down any - // subsequent first-child pointers to the bottom. Otherwise, if there are no - // children then the parent node is the next in the traversal. - m_iterator.assert_valid(); - // go up a level - ntree_node* old_node = m_iterator.node(); - ntree_node* parent = old_node->m_parent; - if (!parent) - { - // we've walked off the top of the tree, so return end - m_iterator.set_end(); - } - else - { - // otherwise find which index the old node was relative to this node - TYPENAME std::vector*>::iterator found = - std::find(parent->m_children.begin(), parent->m_children.end(), old_node); - // if this was found, then see if there is another - found++; - if (found != parent->m_children.end()) - { - // if so traverse to it and walk down the leftmost child pointers to the bottom of the new sub-tree - ntree_node* new_node = *found; - while (!new_node->m_children.empty()) - new_node = new_node->m_children[0]; - m_iterator.set(new_node->m_master); - } - else - { - // the parent's children have all been visited - so the parent is visited - m_iterator.set(parent->m_master); - } - } - return *this; - } - - template - TYPENAME ntree_postfix_iterator::this_iterator ntree_postfix_iterator::operator ++ (int) - throw(null_dereference,end_dereference) - { - // post-increment is defined in terms of the pre-increment - ntree_postfix_iterator result(*this); - ++(*this); - return result; - } - - template - TYPENAME ntree_postfix_iterator::reference ntree_postfix_iterator::operator*(void) const - throw(null_dereference,end_dereference) - { - return m_iterator.operator*(); - } - - template - TYPENAME ntree_postfix_iterator::pointer ntree_postfix_iterator::operator->(void) const - throw(null_dereference,end_dereference) - { - return m_iterator.operator->(); - } - - template - const ntree_iterator& ntree_postfix_iterator::get_iterator(void) const - { - return m_iterator; - } - - template - ntree_iterator& ntree_postfix_iterator::get_iterator(void) - { - return m_iterator; - } - - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - // ntree - //////////////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////////////// - - template - ntree::ntree(void) : m_root(0) - { - } - - template - ntree::~ntree(void) - { - if (m_root) delete m_root; - } - - template - ntree::ntree(const ntree& r) : m_root(0) - { - *this = r; - } - - template - ntree& ntree::operator=(const ntree& r) - { - if (m_root) delete m_root; - m_root = ntree_copy(this, r.m_root); - return *this; - } - - template - bool ntree::empty(void) const - { - return m_root == 0; - } - - template - unsigned ntree::size(void) const - { - return ntree_size(m_root); - } - - template - unsigned ntree::size(const TYPENAME ntree::const_iterator& i) const - throw(wrong_object,null_dereference,end_dereference) - { - i.assert_valid(this); - return ntree_size(i.node()); - } - - template - unsigned ntree::size(const TYPENAME ntree::iterator& i) - throw(wrong_object,null_dereference,end_dereference) - { - i.assert_valid(this); - return ntree_size(i.node()); - } - - template - unsigned ntree::depth(const TYPENAME ntree::const_iterator& i) const - throw(wrong_object,null_dereference,end_dereference) - { - i.assert_valid(this); - return ntree_depth(i.node()); - } - - template - unsigned ntree::depth(const TYPENAME ntree::iterator& i) - throw(wrong_object,null_dereference,end_dereference) - { - i.assert_valid(this); - return ntree_depth(i.node()); - } - - template - TYPENAME ntree::const_iterator ntree::root(void) const - { - if (!m_root) return ntree_iterator(this); - return ntree_iterator(m_root); - } - - template - TYPENAME ntree::iterator ntree::root(void) - { - if (!m_root) return ntree_iterator(this); - return ntree_iterator(m_root); - } - - template - unsigned ntree::children(const TYPENAME ntree::const_iterator& i) const - throw(wrong_object,null_dereference,end_dereference) - { - i.assert_valid(this); - return i.node()->m_children.size(); - } - - template - unsigned ntree::children(const ntree_iterator& i) - throw(wrong_object,null_dereference,end_dereference) - { - i.assert_valid(this); - return i.node()->m_children.size(); - } - - template - TYPENAME ntree::const_iterator ntree::child(const TYPENAME ntree::const_iterator& i, unsigned child) const - throw(wrong_object,null_dereference,end_dereference,std::out_of_range) - { - i.assert_valid(this); - if (child >= children(i)) throw std::out_of_range("stlplus::ntree"); - return ntree_iterator(i.node()->m_children[child]); - } - - template - TYPENAME ntree::iterator ntree::child(const TYPENAME ntree::iterator& i, unsigned child) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range) - { - i.assert_valid(this); - if (child >= children(i)) throw std::out_of_range("stlplus::ntree"); - return ntree_iterator(i.node()->m_children[child]); - } - - template - TYPENAME ntree::const_iterator ntree::parent(const TYPENAME ntree::const_iterator& i) const - throw(wrong_object,null_dereference,end_dereference) - { - i.assert_valid(this); - ntree_node* parent = i.node()->m_parent; - if (!parent) return ntree_iterator(this); - return ntree_iterator(parent); - } - - template - TYPENAME ntree::iterator ntree::parent(const TYPENAME ntree::iterator& i) - throw(wrong_object,null_dereference,end_dereference) - { - i.assert_valid(this); - ntree_node* parent = i.node()->m_parent; - if (!parent) return ntree_iterator(this); - return ntree_iterator(parent); - } - - template - TYPENAME ntree::const_prefix_iterator ntree::prefix_begin(void) const - { - return ntree_prefix_iterator(root()); - } - - template - TYPENAME ntree::prefix_iterator ntree::prefix_begin(void) - { - return ntree_prefix_iterator(root()); - } - - template - TYPENAME ntree::const_prefix_iterator ntree::prefix_end(void) const - { - return ntree_prefix_iterator(ntree_iterator(this)); - } - - template - TYPENAME ntree::prefix_iterator ntree::prefix_end(void) - { - return ntree_prefix_iterator(ntree_iterator(this)); - } - - template - TYPENAME ntree::const_postfix_iterator ntree::postfix_begin(void) const - { - return ntree_postfix_iterator(root()); - } - - template - TYPENAME ntree::postfix_iterator ntree::postfix_begin(void) - { - return ntree_postfix_iterator(root()); - } - - template - TYPENAME ntree::const_postfix_iterator ntree::postfix_end(void) const - { - return ntree_postfix_iterator(ntree_iterator(this)); - } - - template - TYPENAME ntree::postfix_iterator ntree::postfix_end(void) - { - return ntree_postfix_iterator(ntree_iterator(this)); - } - - template - TYPENAME ntree::iterator ntree::insert(const T& data) - { - // insert a new node as the root - return insert(ntree_iterator(this), 0, data); - } - - template - TYPENAME ntree::iterator ntree::insert(const TYPENAME ntree::iterator& i, unsigned offset, const T& data) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range) - { - // if i is the end iterator, this means insert a new root - if (i.end()) - erase(); - else - { - i.assert_valid(this); - if (offset > children(i)) throw std::out_of_range("stlplus::ntree"); - } - ntree_node* new_node = new ntree_node(this,data); - if (i.end()) - { - m_root = new_node; - } - else - { - i.node()->m_children.insert(i.node()->m_children.begin()+offset,new_node); - new_node->m_parent = i.node(); - } - return ntree_iterator(new_node); - } - - template - TYPENAME ntree::iterator ntree::append(const TYPENAME ntree::iterator& i, const T& data) - throw(wrong_object,null_dereference,end_dereference) - { - return insert(i, i.node()->m_children.size(), data); - } - - template - TYPENAME ntree::iterator ntree::insert(const TYPENAME ntree::iterator& i, unsigned offset, const ntree& tree) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range) - { - // insert a whole tree as a child of i - i.assert_valid(this); - if (offset > children(i)) throw std::out_of_range("stlplus::ntree"); - ntree_node* new_node = ntree_copy(this, tree.m_root); - i.node()->m_children.insert(i.node()->m_children.begin()+offset,new_node); - new_node->m_parent = i.node(); - return ntree_iterator(new_node); - } - - template - TYPENAME ntree::iterator ntree::append(const TYPENAME ntree::iterator& i, const ntree& tree) - throw(wrong_object,null_dereference,end_dereference) - { - return insert(i, children(i), tree); - } - - template - TYPENAME ntree::iterator ntree::push(const TYPENAME ntree::iterator& node, const T& data) - throw(wrong_object,null_dereference,end_dereference) - { - // insert a new node to replace the existing node in the tree - // making the original node the child of the new node - // i.e. (node) becomes (new)->(node) - // afterwards, the iterator still points to the old node, now the child - // returns the iterator to the new node - node.assert_valid(this); - ntree_node* new_node = new ntree_node(this,data); - if (node.node() == m_root) - { - // pushing the root node - m_root = new_node; - new_node->m_parent = 0; - } - else - { - // pushing a sub-node - *(std::find(node.node()->m_parent->m_children.begin(), node.node()->m_parent->m_children.end(), node.node())) = new_node; - new_node->m_parent = node.node()->m_parent; - } - // link up the old node as the child of the new node - new_node->m_children.insert(new_node->m_children.begin(),node.node()); - node.node()->m_parent = new_node; - return ntree_iterator(new_node); - } - - template - void ntree::pop(const TYPENAME ntree::iterator& parent, unsigned offset) - throw(wrong_object,null_dereference,end_dereference) - { - // inverse of push - // removes the specified child of the parent node, adding its children to the parent node at the same offset - parent.assert_valid(this); - ntree_node* node = parent.node(); - if (offset >= node->m_children.size()) throw std::out_of_range("stlplus::ntree"); - // move the grandchildren first - ntree_node* child = parent.node()->m_children[offset]; - while (!child->m_children.empty()) - { - // remove the last grandchild and insert into node just after the child to be removed - ntree_node* grandchild = child->m_children[child->m_children.size()-1]; - child->m_children.pop_back(); - node->m_children.insert(node->m_children.begin()+offset+1, grandchild); - grandchild->m_parent = node; - } - // now remove the child - node->m_children.erase(node->m_children.begin()+offset); - delete child; - } - - template - void ntree::erase(void) - { - // erase the whole tree - erase(root()); - } - - template - void ntree::erase(const TYPENAME ntree::iterator& i) - throw(wrong_object,null_dereference,end_dereference) - { - if (!i.end()) - { - // erase this node and its subtree - // do this by erasing this child of its parent - // handle the case of erasing the root - i.assert_valid(this); - ntree_node* node = i.node(); - if (node == m_root) - { - delete m_root; - m_root = 0; - } - else - { - ntree_node* parent = node->m_parent; - // impossible for parent to be null - should assert this - TYPENAME std::vector*>::iterator found = - std::find(parent->m_children.begin(), parent->m_children.end(), node); - // impossible for find to fail - should assert this - parent->m_children.erase(found); - delete node; - } - } - } - - template - void ntree::erase(const TYPENAME ntree::iterator& i, unsigned offset) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range) - { - erase(child(i, offset)); - } - - template - ntree ntree::subtree(void) - { - return subtree(root()); - } - - template - ntree ntree::subtree(const TYPENAME ntree::iterator& i) - throw(wrong_object,null_dereference,end_dereference) - { - ntree result; - if (!i.end()) - { - i.assert_valid(this); - result.m_root = ntree_copy(&result, i.node()); - } - return result; - } - - template - ntree ntree::subtree(const TYPENAME ntree::iterator& i, unsigned offset) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range) - { - return subtree(child(i, offset)); - } - - template - ntree ntree::cut(void) - { - return cut(root()); - } - - template - ntree ntree::cut(const TYPENAME ntree::iterator& i) - throw(wrong_object,null_dereference,end_dereference) - { - ntree result; - if (!i.end()) - { - i.assert_valid(this); - ntree_node* node = i.node(); - if (node == m_root) - { - result.m_root = m_root; - m_root = 0; - } - else - { - ntree_node* parent = node->m_parent; - // impossible for parent to be null - should assert this - TYPENAME std::vector*>::iterator found = - std::find(parent->m_children.begin(), parent->m_children.end(), node); - // impossible for find to fail - should assert this - result.m_root = *found; - parent->m_children.erase(found); - } - if (result.m_root) - { - result.m_root->m_parent = 0; - result.m_root->set_new_owner(&result); - } - } - return result; - } - - template - ntree ntree::cut(const TYPENAME ntree::iterator& i, unsigned offset) - throw(wrong_object,null_dereference,end_dereference,std::out_of_range) - { - return cut(child(i, offset)); - } - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// +#include +#include + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // ntree_node + + template + class ntree_node + { + public: + master_iterator, ntree_node > m_master; + T m_data; + ntree_node* m_parent; + std::vector*> m_children; + + public: + ntree_node(const ntree* owner, const T& data = T()) : + m_master(owner,this), m_data(data), m_parent(0) + { + } + + void change_owner(const ntree* owner) + { + m_master.change_owner(owner); + for (TYPENAME std::vector*>::iterator i = m_children.begin(); i != m_children.end(); i++) + (*i)->change_owner(owner); + } + + ~ntree_node(void) + { + m_parent = 0; + for (TYPENAME std::vector*>::iterator i = m_children.begin(); i != m_children.end(); i++) + delete *i; + } + + }; + + template + static ntree_node* ntree_copy(const ntree* new_owner, ntree_node* root) + { + if (!root) return 0; + ntree_node* new_tree = new ntree_node(new_owner, root->m_data); + for (TYPENAME std::vector*>::iterator i = root->m_children.begin(); i != root->m_children.end(); i++) + { + ntree_node* new_child = ntree_copy(new_owner, *i); + new_tree->m_children.push_back(new_child); + new_child->m_parent = new_tree; + } + return new_tree; + } + + template + static unsigned ntree_size(ntree_node* root) + { + if (!root) return 0; + unsigned result = 1; + for (TYPENAME std::vector*>::iterator i = root->m_children.begin(); i != root->m_children.end(); i++) + result += ntree_size(*i); + return result; + } + + template + static unsigned ntree_depth(ntree_node* root) + { + unsigned depth = 0; + for (ntree_node* i = root; i; i = i->m_parent) + depth++; + return depth; + } + + //////////////////////////////////////////////////////////////////////////////// + // ntree_iterator + + // constructor to create a null iterator - you must assign a valid value to this iterator before using it + template + ntree_iterator::ntree_iterator(void) + { + } + + // used to create an alias of an iterator + template + ntree_iterator::ntree_iterator(const safe_iterator, ntree_node >& iterator) : + safe_iterator,ntree_node >(iterator) + { + } + + // constructor used by ntree to create a non-null iterator + template + ntree_iterator::ntree_iterator(ntree_node* node) : + safe_iterator,ntree_node >(node->m_master) + { + } + + // constructor used by ntree to create an end iterator + template + ntree_iterator::ntree_iterator(const ntree* owner) : + safe_iterator,ntree_node >(owner) + { + } + + // destructor + template + ntree_iterator::~ntree_iterator(void) + { + } + + template + TYPENAME ntree_iterator::const_iterator ntree_iterator::constify(void) const + { + return ntree_iterator(*this); + } + + template + TYPENAME ntree_iterator::iterator ntree_iterator::deconstify(void) const + { + return ntree_iterator(*this); + } + + template + bool ntree_iterator::operator == (const TYPENAME ntree_iterator::this_iterator& r) const + { + return equal(r); + } + + template + bool ntree_iterator::operator != (const TYPENAME ntree_iterator::this_iterator& r) const + { + return !operator==(r); + } + + template + bool ntree_iterator::operator < (const TYPENAME ntree_iterator::this_iterator& r) const + { + return compare(r) < 0; + } + + template + TYPENAME ntree_iterator::reference ntree_iterator::operator*(void) const + throw(null_dereference,end_dereference) + { + this->assert_valid(); + return this->node()->m_data; + } + + template + TYPENAME ntree_iterator::pointer ntree_iterator::operator->(void) const + throw(null_dereference,end_dereference) + { + return &(operator*()); + } + + //////////////////////////////////////////////////////////////////////////////// + // ntree_prefix_iterator + + template + ntree_prefix_iterator::ntree_prefix_iterator(void) + { + } + + template + ntree_prefix_iterator::~ntree_prefix_iterator(void) + { + } + + template + ntree_prefix_iterator::ntree_prefix_iterator(const ntree_iterator& i) : + m_iterator(i) + { + // this is initialised with the root node + // which is also the first node in prefix traversal order + } + + template + bool ntree_prefix_iterator::null(void) const + { + return m_iterator.null(); + } + + template + bool ntree_prefix_iterator::end(void) const + { + return m_iterator.end(); + } + + template + bool ntree_prefix_iterator::valid(void) const + { + return m_iterator.valid(); + } + + template + TYPENAME ntree_prefix_iterator::const_iterator ntree_prefix_iterator::constify(void) const + { + return ntree_prefix_iterator(m_iterator); + } + + template + TYPENAME ntree_prefix_iterator::iterator ntree_prefix_iterator::deconstify(void) const + { + return ntree_prefix_iterator(m_iterator); + } + + template + ntree_iterator ntree_prefix_iterator::simplify(void) const + { + return m_iterator; + } + + template + bool ntree_prefix_iterator::operator == (const TYPENAME ntree_prefix_iterator::this_iterator& r) const + { + return m_iterator == r.m_iterator; + } + + template + bool ntree_prefix_iterator::operator != (const TYPENAME ntree_prefix_iterator::this_iterator& r) const + { + return m_iterator != r.m_iterator; + } + + template + bool ntree_prefix_iterator::operator < (const TYPENAME ntree_prefix_iterator::this_iterator& r) const + { + return m_iterator < r.m_iterator; + } + + template + TYPENAME ntree_prefix_iterator::this_iterator& ntree_prefix_iterator::operator ++ (void) + throw(null_dereference,end_dereference) + { + // pre-increment operator + // algorithm: if there are any children, visit child 0, otherwise, go to + // parent and deduce which child the start node was of that parent - if + // there are further children, go into the next one. Otherwise, go up the + // tree and test again for further children. Return null if there are no + // further nodes + m_iterator.assert_valid(); + ntree_node* old_node = m_iterator.node(); + if (!old_node->m_children.empty()) + { + // simply take the first child of this node + m_iterator.set(old_node->m_children[0]->m_master); + } + else + { + // this loop walks up the parent pointers + // either it will walk off the top and exit or a new node will be found and the loop will exit + for (;;) + { + // go up a level + ntree_node* parent = old_node->m_parent; + if (!parent) + { + // we've walked off the top of the tree, so return end + m_iterator.set_end(); + break; + } + else + { + // otherwise walk down the next child - if there is one + // find which index the old node was relative to this node + TYPENAME std::vector*>::iterator found = + std::find(parent->m_children.begin(), parent->m_children.end(), old_node); + // if this was found, then see if there is another and if so return that + found++; + if (found != parent->m_children.end()) + { + // visit the next child + m_iterator.set((*found)->m_master); + break; + } + else + { + // keep going up + old_node = parent; + } + } + } + } + return *this; + } + + template + TYPENAME ntree_prefix_iterator::this_iterator ntree_prefix_iterator::operator ++ (int) + throw(null_dereference,end_dereference) + { + // post-increment is defined in terms of the pre-increment + ntree_prefix_iterator result(*this); + ++(*this); + return result; + } + + template + TYPENAME ntree_prefix_iterator::reference ntree_prefix_iterator::operator*(void) const + throw(null_dereference,end_dereference) + { + return m_iterator.operator*(); + } + + template + TYPENAME ntree_prefix_iterator::pointer ntree_prefix_iterator::operator->(void) const + throw(null_dereference,end_dereference) + { + return m_iterator.operator->(); + } + + template + const ntree_iterator& ntree_prefix_iterator::get_iterator(void) const + { + return m_iterator; + } + + template + ntree_iterator& ntree_prefix_iterator::get_iterator(void) + { + return m_iterator; + } + + //////////////////////////////////////////////////////////////////////////////// + // ntree_postfix_iterator + + template + ntree_postfix_iterator::ntree_postfix_iterator(void) + { + } + + template + ntree_postfix_iterator::~ntree_postfix_iterator(void) + { + } + + template + ntree_postfix_iterator::ntree_postfix_iterator(const ntree_iterator& i) : + m_iterator(i) + { + // this is initialised with the root node + // initially traverse to the first node to be visited + if (m_iterator.valid()) + { + ntree_node* node = m_iterator.node(); + while (!node->m_children.empty()) + node = node->m_children[0]; + m_iterator.set(node->m_master); + } + } + + template + bool ntree_postfix_iterator::null(void) const + { + return m_iterator.null(); + } + + template + bool ntree_postfix_iterator::end(void) const + { + return m_iterator.end(); + } + + template + bool ntree_postfix_iterator::valid(void) const + { + return m_iterator.valid(); + } + + template + TYPENAME ntree_postfix_iterator::const_iterator ntree_postfix_iterator::constify(void) const + { + return ntree_postfix_iterator(m_iterator); + } + + template + TYPENAME ntree_postfix_iterator::iterator ntree_postfix_iterator::deconstify(void) const + { + return ntree_postfix_iterator(m_iterator); + } + + template + ntree_iterator ntree_postfix_iterator::simplify(void) const + { + return m_iterator; + } + + template + bool ntree_postfix_iterator::operator == (const TYPENAME ntree_postfix_iterator::this_iterator& r) const + { + return m_iterator == r.m_iterator; + } + + template + bool ntree_postfix_iterator::operator != (const TYPENAME ntree_postfix_iterator::this_iterator& r) const + { + return m_iterator != r.m_iterator; + } + + template + bool ntree_postfix_iterator::operator < (const TYPENAME ntree_postfix_iterator::this_iterator& r) const + { + return m_iterator < r.m_iterator; + } + + template + TYPENAME ntree_postfix_iterator::this_iterator& ntree_postfix_iterator::operator ++ (void) + throw(null_dereference,end_dereference) + { + // pre-increment operator + // algorithm: this node has been visited, therefore all children must have + // already been visited. So go to parent. Return null if the parent is null. + // Otherwise deduce which child the start node was of that parent - if there + // are further children, go into the next one and then walk down any + // subsequent first-child pointers to the bottom. Otherwise, if there are no + // children then the parent node is the next in the traversal. + m_iterator.assert_valid(); + // go up a level + ntree_node* old_node = m_iterator.node(); + ntree_node* parent = old_node->m_parent; + if (!parent) + { + // we've walked off the top of the tree, so return end + m_iterator.set_end(); + } + else + { + // otherwise find which index the old node was relative to this node + TYPENAME std::vector*>::iterator found = + std::find(parent->m_children.begin(), parent->m_children.end(), old_node); + // if this was found, then see if there is another + found++; + if (found != parent->m_children.end()) + { + // if so traverse to it and walk down the leftmost child pointers to the bottom of the new sub-tree + ntree_node* new_node = *found; + while (!new_node->m_children.empty()) + new_node = new_node->m_children[0]; + m_iterator.set(new_node->m_master); + } + else + { + // the parent's children have all been visited - so the parent is visited + m_iterator.set(parent->m_master); + } + } + return *this; + } + + template + TYPENAME ntree_postfix_iterator::this_iterator ntree_postfix_iterator::operator ++ (int) + throw(null_dereference,end_dereference) + { + // post-increment is defined in terms of the pre-increment + ntree_postfix_iterator result(*this); + ++(*this); + return result; + } + + template + TYPENAME ntree_postfix_iterator::reference ntree_postfix_iterator::operator*(void) const + throw(null_dereference,end_dereference) + { + return m_iterator.operator*(); + } + + template + TYPENAME ntree_postfix_iterator::pointer ntree_postfix_iterator::operator->(void) const + throw(null_dereference,end_dereference) + { + return m_iterator.operator->(); + } + + template + const ntree_iterator& ntree_postfix_iterator::get_iterator(void) const + { + return m_iterator; + } + + template + ntree_iterator& ntree_postfix_iterator::get_iterator(void) + { + return m_iterator; + } + + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + // ntree + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + + template + ntree::ntree(void) : m_root(0) + { + } + + template + ntree::~ntree(void) + { + if (m_root) delete m_root; + } + + template + ntree::ntree(const ntree& r) : m_root(0) + { + *this = r; + } + + template + ntree& ntree::operator=(const ntree& r) + { + if (m_root) delete m_root; + m_root = ntree_copy(this, r.m_root); + return *this; + } + + template + bool ntree::empty(void) const + { + return m_root == 0; + } + + template + unsigned ntree::size(void) const + { + return ntree_size(m_root); + } + + template + unsigned ntree::size(const TYPENAME ntree::const_iterator& i) const + throw(wrong_object,null_dereference,end_dereference) + { + i.assert_valid(this); + return ntree_size(i.node()); + } + + template + unsigned ntree::size(const TYPENAME ntree::iterator& i) + throw(wrong_object,null_dereference,end_dereference) + { + i.assert_valid(this); + return ntree_size(i.node()); + } + + template + unsigned ntree::depth(const TYPENAME ntree::const_iterator& i) const + throw(wrong_object,null_dereference,end_dereference) + { + i.assert_valid(this); + return ntree_depth(i.node()); + } + + template + unsigned ntree::depth(const TYPENAME ntree::iterator& i) + throw(wrong_object,null_dereference,end_dereference) + { + i.assert_valid(this); + return ntree_depth(i.node()); + } + + template + TYPENAME ntree::const_iterator ntree::root(void) const + { + if (!m_root) return ntree_iterator(this); + return ntree_iterator(m_root); + } + + template + TYPENAME ntree::iterator ntree::root(void) + { + if (!m_root) return ntree_iterator(this); + return ntree_iterator(m_root); + } + + template + unsigned ntree::children(const TYPENAME ntree::const_iterator& i) const + throw(wrong_object,null_dereference,end_dereference) + { + i.assert_valid(this); + return i.node()->m_children.size(); + } + + template + unsigned ntree::children(const ntree_iterator& i) + throw(wrong_object,null_dereference,end_dereference) + { + i.assert_valid(this); + return i.node()->m_children.size(); + } + + template + TYPENAME ntree::const_iterator ntree::child(const TYPENAME ntree::const_iterator& i, unsigned child) const + throw(wrong_object,null_dereference,end_dereference,std::out_of_range) + { + i.assert_valid(this); + if (child >= children(i)) throw std::out_of_range("stlplus::ntree"); + return ntree_iterator(i.node()->m_children[child]); + } + + template + TYPENAME ntree::iterator ntree::child(const TYPENAME ntree::iterator& i, unsigned child) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range) + { + i.assert_valid(this); + if (child >= children(i)) throw std::out_of_range("stlplus::ntree"); + return ntree_iterator(i.node()->m_children[child]); + } + + template + TYPENAME ntree::const_iterator ntree::parent(const TYPENAME ntree::const_iterator& i) const + throw(wrong_object,null_dereference,end_dereference) + { + i.assert_valid(this); + ntree_node* parent = i.node()->m_parent; + if (!parent) return ntree_iterator(this); + return ntree_iterator(parent); + } + + template + TYPENAME ntree::iterator ntree::parent(const TYPENAME ntree::iterator& i) + throw(wrong_object,null_dereference,end_dereference) + { + i.assert_valid(this); + ntree_node* parent = i.node()->m_parent; + if (!parent) return ntree_iterator(this); + return ntree_iterator(parent); + } + + template + TYPENAME ntree::const_prefix_iterator ntree::prefix_begin(void) const + { + return ntree_prefix_iterator(root()); + } + + template + TYPENAME ntree::prefix_iterator ntree::prefix_begin(void) + { + return ntree_prefix_iterator(root()); + } + + template + TYPENAME ntree::const_prefix_iterator ntree::prefix_end(void) const + { + return ntree_prefix_iterator(ntree_iterator(this)); + } + + template + TYPENAME ntree::prefix_iterator ntree::prefix_end(void) + { + return ntree_prefix_iterator(ntree_iterator(this)); + } + + template + TYPENAME ntree::const_postfix_iterator ntree::postfix_begin(void) const + { + return ntree_postfix_iterator(root()); + } + + template + TYPENAME ntree::postfix_iterator ntree::postfix_begin(void) + { + return ntree_postfix_iterator(root()); + } + + template + TYPENAME ntree::const_postfix_iterator ntree::postfix_end(void) const + { + return ntree_postfix_iterator(ntree_iterator(this)); + } + + template + TYPENAME ntree::postfix_iterator ntree::postfix_end(void) + { + return ntree_postfix_iterator(ntree_iterator(this)); + } + + template + TYPENAME ntree::iterator ntree::insert(const T& data) + { + // insert a new node as the root + erase(); + m_root = new ntree_node(this,data); + return ntree_iterator(m_root); + } + + template + TYPENAME ntree::iterator ntree::insert(const TYPENAME ntree::iterator& i, unsigned offset, const T& data) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range) + { + // if i is the end iterator, this means insert a new root + // if (i.end()) + // return insert(data); + // otherwise, insert a new child + i.assert_valid(this); + if (offset > children(i)) throw std::out_of_range("stlplus::ntree"); + ntree_node* new_node = new ntree_node(this,data); + i.node()->m_children.insert(i.node()->m_children.begin()+offset,new_node); + new_node->m_parent = i.node(); + return ntree_iterator(new_node); + } + + template + TYPENAME ntree::iterator ntree::insert(const TYPENAME ntree::iterator& i, const T& data) + throw(wrong_object,null_dereference,end_dereference) + { + return insert(i, children(i), data); + } + + template + TYPENAME ntree::iterator ntree::append(const TYPENAME ntree::iterator& i, const T& data) + throw(wrong_object,null_dereference,end_dereference) + { + return insert(i, children(i), data); + } + + template + TYPENAME ntree::iterator ntree::insert(const ntree& tree) + { + // insert a whole tree as root + erase(); + m_root = ntree_copy(this, tree.m_root); + return ntree_iterator(m_root); + } + + template + TYPENAME ntree::iterator ntree::insert(const TYPENAME ntree::iterator& i, unsigned offset, const ntree& tree) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range) + { + // insert a whole tree as a child of i + i.assert_valid(this); + if (offset > children(i)) throw std::out_of_range("stlplus::ntree"); + ntree_node* new_node = ntree_copy(this, tree.m_root); + i.node()->m_children.insert(i.node()->m_children.begin()+offset,new_node); + new_node->m_parent = i.node(); + return ntree_iterator(new_node); + } + + template + TYPENAME ntree::iterator ntree::insert(const TYPENAME ntree::iterator& i, const ntree& tree) + throw(wrong_object,null_dereference,end_dereference) + { + return insert(i, children(i), tree); + } + + template + TYPENAME ntree::iterator ntree::append(const TYPENAME ntree::iterator& i, const ntree& tree) + throw(wrong_object,null_dereference,end_dereference) + { + return insert(i, children(i), tree); + } + + template + TYPENAME ntree::iterator ntree::move(ntree& tree) + { + // insert a whole tree as root, removing it from source + erase(); + m_root = tree.m_root; + tree.m_root = 0; + if (m_root) m_root->change_owner(this); + return ntree_iterator(m_root); + } + + template + TYPENAME ntree::iterator ntree::move(const TYPENAME ntree::iterator& i, unsigned offset, ntree& tree) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range) + { + // insert a whole tree as a child of i + i.assert_valid(this); + if (offset > children(i)) throw std::out_of_range("stlplus::ntree"); + ntree_node* new_node = tree.m_root; + tree.m_root = 0; + if (new_node) new_node->change_owner(this); + i.node()->m_children.insert(i.node()->m_children.begin()+offset,new_node); + new_node->m_parent = i.node(); + return ntree_iterator(new_node); + } + + template + TYPENAME ntree::iterator ntree::move(const TYPENAME ntree::iterator& i, ntree& tree) + throw(wrong_object,null_dereference,end_dereference) + { + return move(i, children(i), tree); + } + + template + TYPENAME ntree::iterator ntree::push(const TYPENAME ntree::iterator& node, const T& data) + throw(wrong_object,null_dereference,end_dereference) + { + // insert a new node to replace the existing node in the tree + // making the original node the child of the new node + // i.e. (node) becomes (new)->(node) + // afterwards, the iterator still points to the old node, now the child + // returns the iterator to the new node + node.assert_valid(this); + ntree_node* new_node = new ntree_node(this,data); + if (node.node() == m_root) + { + // pushing the root node + m_root = new_node; + new_node->m_parent = 0; + } + else + { + // pushing a sub-node + *(std::find(node.node()->m_parent->m_children.begin(), node.node()->m_parent->m_children.end(), node.node())) = new_node; + new_node->m_parent = node.node()->m_parent; + } + // link up the old node as the child of the new node + new_node->m_children.insert(new_node->m_children.begin(),node.node()); + node.node()->m_parent = new_node; + return ntree_iterator(new_node); + } + + template + void ntree::pop(const TYPENAME ntree::iterator& parent, unsigned offset) + throw(wrong_object,null_dereference,end_dereference) + { + // inverse of push + // removes the specified child of the parent node, adding its children to the parent node at the same offset + parent.assert_valid(this); + ntree_node* node = parent.node(); + if (offset >= node->m_children.size()) throw std::out_of_range("stlplus::ntree"); + // move the grandchildren first + ntree_node* child = parent.node()->m_children[offset]; + while (!child->m_children.empty()) + { + // remove the last grandchild and insert into node just after the child to be removed + ntree_node* grandchild = child->m_children[child->m_children.size()-1]; + child->m_children.pop_back(); + node->m_children.insert(node->m_children.begin()+offset+1, grandchild); + grandchild->m_parent = node; + } + // now remove the child + node->m_children.erase(node->m_children.begin()+offset); + delete child; + } + + template + void ntree::erase(void) + { + // erase the whole tree + erase(root()); + } + + template + void ntree::erase(const TYPENAME ntree::iterator& i) + throw(wrong_object,null_dereference,end_dereference) + { + if (!i.end()) + { + // erase this node and its subtree + // do this by erasing this child of its parent + // handle the case of erasing the root + i.assert_valid(this); + ntree_node* node = i.node(); + if (node == m_root) + { + delete m_root; + m_root = 0; + } + else + { + ntree_node* parent = node->m_parent; + // impossible for parent to be null - should assert this + TYPENAME std::vector*>::iterator found = + std::find(parent->m_children.begin(), parent->m_children.end(), node); + // impossible for find to fail - should assert this + parent->m_children.erase(found); + delete node; + } + } + } + + template + void ntree::erase(const TYPENAME ntree::iterator& i, unsigned offset) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range) + { + erase(child(i, offset)); + } + + template + ntree ntree::subtree(void) + { + return subtree(root()); + } + + template + ntree ntree::subtree(const TYPENAME ntree::iterator& i) + throw(wrong_object,null_dereference,end_dereference) + { + ntree result; + if (!i.end()) + { + i.assert_valid(this); + result.m_root = ntree_copy(&result, i.node()); + } + return result; + } + + template + ntree ntree::subtree(const TYPENAME ntree::iterator& i, unsigned offset) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range) + { + return subtree(child(i, offset)); + } + + template + ntree ntree::cut(void) + { + return cut(root()); + } + + template + ntree ntree::cut(const TYPENAME ntree::iterator& i) + throw(wrong_object,null_dereference,end_dereference) + { + ntree result; + if (!i.end()) + { + i.assert_valid(this); + ntree_node* node = i.node(); + if (node == m_root) + { + result.m_root = m_root; + m_root = 0; + } + else + { + ntree_node* parent = node->m_parent; + // impossible for parent to be null - should assert this + TYPENAME std::vector*>::iterator found = + std::find(parent->m_children.begin(), parent->m_children.end(), node); + // impossible for find to fail - should assert this + result.m_root = *found; + parent->m_children.erase(found); + } + if (result.m_root) + { + result.m_root->m_parent = 0; + result.m_root->set_new_owner(&result); + } + } + return result; + } + + template + ntree ntree::cut(const TYPENAME ntree::iterator& i, unsigned offset) + throw(wrong_object,null_dereference,end_dereference,std::out_of_range) + { + return cut(child(i, offset)); + } + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + diff --git a/src/stlplus/containers/safe_iterator.hpp b/src/stlplus/containers/safe_iterator.hpp index 7412687..6d8b563 100644 --- a/src/stlplus/containers/safe_iterator.hpp +++ b/src/stlplus/containers/safe_iterator.hpp @@ -1,155 +1,155 @@ -#ifndef STLPLUS_SAFE_ITERATOR -#define STLPLUS_SAFE_ITERATOR -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// The STLplus safe_iterator superclasses. This implements the STLplus safe -// iterator principles. Data structures can then be built using subclasses -// of safe_iterator for their iterator objects and they will inherit the -// safe iterator behaviour. - -// The data structure must contain a master iterator for each node in the -// structure. When an iterator is returned to the user, it must be created -// by the master iterator. When a node is removed from the data structure, -// its master iterator is destroyed. This sets all iterators pointing to the -// master iterator to end iterators. - -//////////////////////////////////////////////////////////////////////////////// -#include "containers_fixes.hpp" -#include "exceptions.hpp" - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // internals - - template - class safe_iterator_body; - - template - class safe_iterator; - - //////////////////////////////////////////////////////////////////////////////// - // Master Iterator - // Create one of these in each node in the data structure - // Generate iterators by obtaining a safe-iterator object from the master iterator - //////////////////////////////////////////////////////////////////////////////// - - template - class master_iterator - { - public: - - // construct a valid master iterator connected to the node - master_iterator(const O* owner, N* node) throw(); - - // destructor - disconnects all iterators from the node - ~master_iterator(void) throw(); - - // dereference - N* node(void) const throw(); - const O* owner(void) const throw(); - - // when you move a node from one owner to another, call this on the node's master iterator - // this effectively moves all other iterators to the node so that they are owned by the new owner too - void change_owner(const O* owner) throw(); - - friend class safe_iterator; - private: - master_iterator(const master_iterator&) throw(); - master_iterator& operator=(const master_iterator&) throw(); - safe_iterator_body* m_body; - }; - - //////////////////////////////////////////////////////////////////////////////// - // Safe Iterator - //////////////////////////////////////////////////////////////////////////////// - - template - class safe_iterator - { - public: - - // construct a null iterator - safe_iterator(void) throw(); - - // construct a valid iterator by aliasing from the owner node's master iterator - safe_iterator(const master_iterator&) throw(); - - // copy constructor does aliasing - safe_iterator(const safe_iterator&) throw(); - - // alias an iterator by assignment - safe_iterator& operator=(const safe_iterator&) throw(); - - // destructor - ~safe_iterator(void) throw(); - - // reassignment to another node used in increment/decrement operation - void set(const master_iterator&) throw(); - - // dereference - N* node(void) const throw(); - const O* owner(void) const throw(); - - // change to a null iterator - i.e. one that does not belong to any object - // this does not affect any other iterators pointing to the same node - void set_null(void) throw(); - - //////////////////////////////////////////////////////////////////////////////// - // operations for clients that do not have a master end iterator - // alternatively, have a master end iterator as part of the container - // and call constructor(master_end) or set(master_end) - - // construct an end iterator - safe_iterator(const O* owner) throw(); - - // change to an end iterator - e.g. as a result of incrementing off the end - void set_end(void) throw(); - - //////////////////////////////////////////////////////////////////////////////// - // tests - - // comparison - bool equal(const safe_iterator& right) const throw(); - int compare(const safe_iterator& right) const throw(); - - // a null iterator is one that has not been initialised with a value yet - // i.e. you just declared it but didn't assign to it - bool null(void) const throw(); - - // an end iterator is one that points to the end element of the list of nodes - // in STL conventions this is one past the last valid element and must not be dereferenced - bool end(void) const throw(); - - // a valid iterator is one that can be dereferenced - // i.e. non-null and non-end - bool valid(void) const throw(); - - // check the rules for a valid iterator that can be dereferenced - // optionally also check that the iterator is owned by the owner - void assert_valid(void) const throw(null_dereference,end_dereference); - void assert_valid(const O* owner) const throw(wrong_object,null_dereference,end_dereference); - // assert the rules for a non-null iterator - i.e. valid or end, values that occur in increment operations - void assert_non_null(void) const throw(null_dereference); - // assert that this iterator is owned by this container - void assert_owner(const O* owner) const throw(wrong_object); - - //////////////////////////////////////////////////////////////////////////////// - - friend class master_iterator; - private: - safe_iterator_body* m_body; - }; - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#include "safe_iterator.tpp" -#endif +#ifndef STLPLUS_SAFE_ITERATOR +#define STLPLUS_SAFE_ITERATOR +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// The STLplus safe_iterator superclasses. This implements the STLplus safe +// iterator principles. Data structures can then be built using subclasses +// of safe_iterator for their iterator objects and they will inherit the +// safe iterator behaviour. + +// The data structure must contain a master iterator for each node in the +// structure. When an iterator is returned to the user, it must be created +// by the master iterator. When a node is removed from the data structure, +// its master iterator is destroyed. This sets all iterators pointing to the +// master iterator to end iterators. + +//////////////////////////////////////////////////////////////////////////////// +#include "containers_fixes.hpp" +#include "exceptions.hpp" + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // internals + + template + class safe_iterator_body; + + template + class safe_iterator; + + //////////////////////////////////////////////////////////////////////////////// + // Master Iterator + // Create one of these in each node in the data structure + // Generate iterators by obtaining a safe-iterator object from the master iterator + //////////////////////////////////////////////////////////////////////////////// + + template + class master_iterator + { + public: + + // construct a valid master iterator connected to the node + master_iterator(const O* owner, N* node) throw(); + + // destructor - disconnects all iterators from the node + ~master_iterator(void) throw(); + + // dereference + N* node(void) const throw(); + const O* owner(void) const throw(); + + // when you move a node from one owner to another, call this on the node's master iterator + // this effectively moves all other iterators to the node so that they are owned by the new owner too + void change_owner(const O* owner) throw(); + + friend class safe_iterator; + private: + master_iterator(const master_iterator&) throw(); + master_iterator& operator=(const master_iterator&) throw(); + safe_iterator_body* m_body; + }; + + //////////////////////////////////////////////////////////////////////////////// + // Safe Iterator + //////////////////////////////////////////////////////////////////////////////// + + template + class safe_iterator + { + public: + + // construct a null iterator + safe_iterator(void) throw(); + + // construct a valid iterator by aliasing from the owner node's master iterator + safe_iterator(const master_iterator&) throw(); + + // copy constructor does aliasing + safe_iterator(const safe_iterator&) throw(); + + // alias an iterator by assignment + safe_iterator& operator=(const safe_iterator&) throw(); + + // destructor + ~safe_iterator(void) throw(); + + // reassignment to another node used in increment/decrement operation + void set(const master_iterator&) throw(); + + // dereference + N* node(void) const throw(); + const O* owner(void) const throw(); + + // change to a null iterator - i.e. one that does not belong to any object + // this does not affect any other iterators pointing to the same node + void set_null(void) throw(); + + //////////////////////////////////////////////////////////////////////////////// + // operations for clients that do not have a master end iterator + // alternatively, have a master end iterator as part of the container + // and call constructor(master_end) or set(master_end) + + // construct an end iterator + safe_iterator(const O* owner) throw(); + + // change to an end iterator - e.g. as a result of incrementing off the end + void set_end(void) throw(); + + //////////////////////////////////////////////////////////////////////////////// + // tests + + // comparison + bool equal(const safe_iterator& right) const throw(); + int compare(const safe_iterator& right) const throw(); + + // a null iterator is one that has not been initialised with a value yet + // i.e. you just declared it but didn't assign to it + bool null(void) const throw(); + + // an end iterator is one that points to the end element of the list of nodes + // in STL conventions this is one past the last valid element and must not be dereferenced + bool end(void) const throw(); + + // a valid iterator is one that can be dereferenced + // i.e. non-null and non-end + bool valid(void) const throw(); + + // check the rules for a valid iterator that can be dereferenced + // optionally also check that the iterator is owned by the owner + void assert_valid(void) const throw(null_dereference,end_dereference); + void assert_valid(const O* owner) const throw(wrong_object,null_dereference,end_dereference); + // assert the rules for a non-null iterator - i.e. valid or end, values that occur in increment operations + void assert_non_null(void) const throw(null_dereference); + // assert that this iterator is owned by this container + void assert_owner(const O* owner) const throw(wrong_object); + + //////////////////////////////////////////////////////////////////////////////// + + friend class master_iterator; + private: + safe_iterator_body* m_body; + }; + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#include "safe_iterator.tpp" +#endif diff --git a/src/stlplus/containers/safe_iterator.tpp b/src/stlplus/containers/safe_iterator.tpp index 3296482..4bbf9b8 100644 --- a/src/stlplus/containers/safe_iterator.tpp +++ b/src/stlplus/containers/safe_iterator.tpp @@ -1,373 +1,357 @@ -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // body class implements the aliasing behaviour - - template - class safe_iterator_body - { - private: - const O* m_owner; - N* m_node; - unsigned m_count; - - public: - - safe_iterator_body(const O* owner, N* node) throw() : - m_owner(owner), m_node(node), m_count(1) - { -// std::cerr << "constructing " -// << std::hex << ((unsigned long)this) -// << " => " << ((unsigned long)m_owner) << ":" << ((unsigned long)m_node) -// << ":" << std::dec << m_count << std::endl; - } - - ~safe_iterator_body(void) throw() - { -// std::cerr << "destroying " -// << std::hex << ((unsigned long)this) -// << " => " << ((unsigned long)m_owner) << ":" << ((unsigned long)m_node) -// << ":" << std::dec << m_count << std::endl; - m_owner = 0; - m_node = 0; - } - - unsigned count(void) const - { - return m_count; - } - - void increment(void) - { - ++m_count; -// std::cerr << "incremented " -// << std::hex << ((unsigned long)this) -// << " => " << ((unsigned long)m_owner) << ":" << ((unsigned long)m_node) -// << ":" << std::dec << m_count << std::endl; - } - - bool decrement(void) - { - --m_count; -// std::cerr << "decremented " -// << std::hex << ((unsigned long)this) -// << " => " << ((unsigned long)m_owner) << ":" << ((unsigned long)m_node) -// << ":" << std::dec << m_count << std::endl; - return m_count == 0; - } - - N* node(void) const throw() - { - return m_node; - } - - const O* owner(void) const throw() - { - return m_owner; - } - - void change_owner(const O* owner) - { - m_owner = owner; - } - - bool equal(const safe_iterator_body* right) const throw() - { -// return m_node == right->m_node; - return compare(right) == 0; - } - - int compare(const safe_iterator_body* right) const throw() - { - return ((long)m_node) - ((long)right->m_node); - } - - bool null(void) const throw() - { - return m_owner == 0; - } - - bool end(void) const throw() - { - return m_owner != 0 && m_node == 0; - } - - bool valid(void) const throw() - { - return m_owner != 0 && m_node != 0; - } - - void set_end(void) throw() - { - m_node = 0; - } - - void set_null(void) throw() - { - m_owner = 0; - m_node = 0; - } - - void assert_valid(void) const throw(null_dereference,end_dereference) - { - if (null()) - throw null_dereference("stlplus::safe_iterator"); - if (end()) - throw end_dereference("stlplus::safe_iterator"); - } - - void assert_non_null(void) const throw(null_dereference) - { - if (null()) - throw null_dereference("stlplus::safe_iterator"); - } - - void assert_owner(const O* owner) const throw(wrong_object) - { - if (owner != m_owner) - throw wrong_object("stlplus::safe_iterator"); - } - }; - - - //////////////////////////////////////////////////////////////////////////////// - // Master Iterator - //////////////////////////////////////////////////////////////////////////////// - - // construct a valid iterator - template - master_iterator::master_iterator(const O* owner, N* node) throw() : - m_body(new safe_iterator_body(owner,node)) - { - } - - // destructor - disconnect all iterators from the node - // this usually happens when the node is deleted and must invalidate all aliases - template - master_iterator::~master_iterator(void) throw() - { - m_body->set_end(); - if(m_body->decrement()) - { - delete m_body; - m_body = 0; - } - } - - // dereference - template - N* master_iterator::node(void) const throw() - { - return m_body->node(); - } - - template - const O* master_iterator::owner(void) const throw() - { - return m_body->owner(); - } - - // when you move a node from one owner to another, call this on the node's iterator - // this effectively moves all iterators to the node so that they are owned by the new owner too - template - void master_iterator::change_owner(const O* owner) throw() - { - m_body->change_owner(owner); - } - - //////////////////////////////////////////////////////////////////////////////// - // Safe Iterator - //////////////////////////////////////////////////////////////////////////////// - - // construct a null iterator - // later assignment of a valid iterator to this is done by using step - template - safe_iterator::safe_iterator(void) throw() : - m_body(new safe_iterator_body(0,0)) - { - } - - // construct a valid iterator by aliasing from the owner node's master iterator - template - safe_iterator::safe_iterator(const master_iterator& r) throw() : - m_body(0) - { - m_body = r.m_body; - m_body->increment(); - } - - // construct a valid iterator by aliasing from the owner node's master iterator - template - safe_iterator::safe_iterator(const safe_iterator& r) throw() : - m_body(0) - { - m_body = r.m_body; - m_body->increment(); - } - - // assignment implements dealiasing followed by aliasing - template - safe_iterator& safe_iterator::operator=(const safe_iterator& r) throw() - { - if (m_body != r.m_body) - { - if (m_body->decrement()) - delete m_body; - m_body = r.m_body; - m_body->increment(); - } - return *this; - } - - // destructor - implements dealiasing - template - safe_iterator::~safe_iterator(void) throw() - { - if(m_body->decrement()) - { - delete m_body; - m_body = 0; - } - } - - - // increment/decrement operation - // implements dealiasing followed by aliasing - template - void safe_iterator::set(const master_iterator& r) throw() - { - if (m_body != r.m_body) - { - if (m_body->decrement()) - delete m_body; - m_body = r.m_body; - m_body->increment(); - } - } - - // dereference - template - N* safe_iterator::node(void) const throw() - { - return m_body->node(); - } - - template - const O* safe_iterator::owner(void) const throw() - { - return m_body->owner(); - } - - // change to a null iterator - i.e. one that doees not belong to any object - // this does not affect any other iterators pointing to the same node - template - void safe_iterator::set_null(void) throw() - { - if (m_body->count() == 1) - { - // no aliases, so just make this null - m_body->set_null(); - } - else - { - // create a new body which is null so as not to affect any other aliases - m_body->decrement(); - m_body = new safe_iterator_body(0,0); - } - } - - //////////////////////////////////////////////////////////////////////////////// - // operations for clients that do not have a master end iterator - // alternatively, have a master end iterator as part of the container - // and call constructor(master_end) or step(master_end) - - // construct an end iterator - template - safe_iterator::safe_iterator(const O* owner) throw() : - m_body(new safe_iterator_body(owner,0)) - { - } - - // change to an end iterator - e.g. as a result of incrementing off the end - template - void safe_iterator::set_end(void) throw() - { - if (m_body->count() == 1) - { - // no aliases, so just make this an end iterator - m_body->set_end(); - } - else - { - // create a new body which is null so as not to affect any other aliases - m_body->decrement(); - m_body = new safe_iterator_body(owner(),0); - } - } - - //////////////////////////////////////////////////////////////////////////////// - // tests - - // comparison - template - bool safe_iterator::equal(const safe_iterator& right) const throw() - { - return compare(right) == 0; - } - - template - int safe_iterator::compare(const safe_iterator& right) const throw() - { - if (m_body == right.m_body) return 0; - return m_body->compare(right.m_body); - } - - // a null iterator is one that has not been initialised with a value yet - template - bool safe_iterator::null(void) const throw() - { - return m_body->null(); - } - - // an end iterator is one that points to the end element of the list of nodes - template - bool safe_iterator::end(void) const throw() - { - return m_body->end(); - } - - // a valid iterator is one that can be dereferenced - template - bool safe_iterator::valid(void) const throw() - { - return m_body->valid(); - } - - // check the rules for a valid iterator that can be dereferenced - template - void safe_iterator::assert_valid(void) const throw(null_dereference,end_dereference) - { - m_body->assert_valid(); - } - - template - void safe_iterator::assert_valid(const O* owner) const throw(wrong_object,null_dereference,end_dereference) - { - m_body->assert_valid(); - m_body->assert_owner(owner); - } - - template - void safe_iterator::assert_non_null(void) const throw(null_dereference) - { - m_body->assert_non_null(); - } - - template - void safe_iterator::assert_owner(const O* owner) const throw(wrong_object) - { - m_body->assert_owner(owner); - } - -} // end namespace stlplus +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // body class implements the aliasing behaviour + + template + class safe_iterator_body + { + private: + const O* m_owner; + N* m_node; + unsigned m_count; + + public: + + safe_iterator_body(const O* owner, N* node) throw() : + m_owner(owner), m_node(node), m_count(1) + { + } + + ~safe_iterator_body(void) throw() + { + m_owner = 0; + m_node = 0; + } + + unsigned count(void) const + { + return m_count; + } + + void increment(void) + { + ++m_count; + } + + bool decrement(void) + { + --m_count; + return m_count == 0; + } + + N* node(void) const throw() + { + return m_node; + } + + const O* owner(void) const throw() + { + return m_owner; + } + + void change_owner(const O* owner) + { + m_owner = owner; + } + + bool equal(const safe_iterator_body* right) const throw() + { + return m_node == right->m_node; + } + + int compare(const safe_iterator_body* right) const throw() + { + if (m_node == right->m_node) return 0; + return (m_node < right->m_node) ? -1 : 1; + } + + bool null(void) const throw() + { + return m_owner == 0; + } + + bool end(void) const throw() + { + return m_owner != 0 && m_node == 0; + } + + bool valid(void) const throw() + { + return m_owner != 0 && m_node != 0; + } + + void set_end(void) throw() + { + m_node = 0; + } + + void set_null(void) throw() + { + m_owner = 0; + m_node = 0; + } + + void assert_valid(void) const throw(null_dereference,end_dereference) + { + if (null()) + throw null_dereference("stlplus::safe_iterator: dereferencing null iterator"); + if (end()) + throw end_dereference("stlplus::safe_iterator: dereferencing end iterator"); + } + + void assert_non_null(void) const throw(null_dereference) + { + if (null()) + throw null_dereference("stlplus::safe_iterator: dereferencing null iterator"); + } + + void assert_owner(const O* owner) const throw(wrong_object) + { + if (owner != m_owner) + throw wrong_object("stlplus::safe_iterator: using iterator with wrong object"); + } + }; + + + //////////////////////////////////////////////////////////////////////////////// + // Master Iterator + //////////////////////////////////////////////////////////////////////////////// + + // construct a valid iterator + template + master_iterator::master_iterator(const O* owner, N* node) throw() : + m_body(new safe_iterator_body(owner,node)) + { + } + + // destructor - disconnect all iterators from the node + // this usually happens when the node is deleted and must invalidate all aliases + template + master_iterator::~master_iterator(void) throw() + { + m_body->set_end(); + if(m_body->decrement()) + { + delete m_body; + m_body = 0; + } + } + + // dereference + template + N* master_iterator::node(void) const throw() + { + return m_body->node(); + } + + template + const O* master_iterator::owner(void) const throw() + { + return m_body->owner(); + } + + // when you move a node from one owner to another, call this on the node's iterator + // this effectively moves all iterators to the node so that they are owned by the new owner too + template + void master_iterator::change_owner(const O* owner) throw() + { + m_body->change_owner(owner); + } + + //////////////////////////////////////////////////////////////////////////////// + // Safe Iterator + //////////////////////////////////////////////////////////////////////////////// + + // construct a null iterator + // later assignment of a valid iterator to this is done by using step + template + safe_iterator::safe_iterator(void) throw() : + m_body(new safe_iterator_body(0,0)) + { + } + + // construct a valid iterator by aliasing from the owner node's master iterator + template + safe_iterator::safe_iterator(const master_iterator& r) throw() : + m_body(0) + { + m_body = r.m_body; + m_body->increment(); + } + + // construct a valid iterator by aliasing from the owner node's master iterator + template + safe_iterator::safe_iterator(const safe_iterator& r) throw() : + m_body(0) + { + m_body = r.m_body; + m_body->increment(); + } + + // assignment implements dealiasing followed by aliasing + template + safe_iterator& safe_iterator::operator=(const safe_iterator& r) throw() + { + if (m_body != r.m_body) + { + if (m_body->decrement()) + delete m_body; + m_body = r.m_body; + m_body->increment(); + } + return *this; + } + + // destructor - implements dealiasing + template + safe_iterator::~safe_iterator(void) throw() + { + if(m_body->decrement()) + { + delete m_body; + m_body = 0; + } + } + + + // increment/decrement operation + // implements dealiasing followed by aliasing + template + void safe_iterator::set(const master_iterator& r) throw() + { + if (m_body != r.m_body) + { + if (m_body->decrement()) + delete m_body; + m_body = r.m_body; + m_body->increment(); + } + } + + // dereference + template + N* safe_iterator::node(void) const throw() + { + return m_body->node(); + } + + template + const O* safe_iterator::owner(void) const throw() + { + return m_body->owner(); + } + + // change to a null iterator - i.e. one that doees not belong to any object + // this does not affect any other iterators pointing to the same node + template + void safe_iterator::set_null(void) throw() + { + if (m_body->count() == 1) + { + // no aliases, so just make this null + m_body->set_null(); + } + else + { + // create a new body which is null so as not to affect any other aliases + m_body->decrement(); + m_body = new safe_iterator_body(0,0); + } + } + + //////////////////////////////////////////////////////////////////////////////// + // operations for clients that do not have a master end iterator + // alternatively, have a master end iterator as part of the container + // and call constructor(master_end) or step(master_end) + + // construct an end iterator + template + safe_iterator::safe_iterator(const O* owner) throw() : + m_body(new safe_iterator_body(owner,0)) + { + } + + // change to an end iterator - e.g. as a result of incrementing off the end + template + void safe_iterator::set_end(void) throw() + { + if (m_body->count() == 1) + { + // no aliases, so just make this an end iterator + m_body->set_end(); + } + else + { + // create a new body which is null so as not to affect any other aliases + m_body->decrement(); + m_body = new safe_iterator_body(owner(),0); + } + } + + //////////////////////////////////////////////////////////////////////////////// + // tests + + // comparison + template + bool safe_iterator::equal(const safe_iterator& right) const throw() + { + if (m_body == right.m_body) return true; + return m_body->equal(right.m_body); + } + + template + int safe_iterator::compare(const safe_iterator& right) const throw() + { + return m_body->compare(right.m_body); + } + + // a null iterator is one that has not been initialised with a value yet + template + bool safe_iterator::null(void) const throw() + { + return m_body->null(); + } + + // an end iterator is one that points to the end element of the list of nodes + template + bool safe_iterator::end(void) const throw() + { + return m_body->end(); + } + + // a valid iterator is one that can be dereferenced + template + bool safe_iterator::valid(void) const throw() + { + return m_body->valid(); + } + + // check the rules for a valid iterator that can be dereferenced + template + void safe_iterator::assert_valid(void) const throw(null_dereference,end_dereference) + { + m_body->assert_valid(); + } + + template + void safe_iterator::assert_valid(const O* owner) const throw(wrong_object,null_dereference,end_dereference) + { + m_body->assert_valid(); + m_body->assert_owner(owner); + } + + template + void safe_iterator::assert_non_null(void) const throw(null_dereference) + { + m_body->assert_non_null(); + } + + template + void safe_iterator::assert_owner(const O* owner) const throw(wrong_object) + { + m_body->assert_owner(owner); + } + +} // end namespace stlplus diff --git a/src/stlplus/containers/simple_ptr.hpp b/src/stlplus/containers/simple_ptr.hpp index 72d1db7..ffe2c8a 100644 --- a/src/stlplus/containers/simple_ptr.hpp +++ b/src/stlplus/containers/simple_ptr.hpp @@ -1,264 +1,282 @@ -#ifndef STLPLUS_SIMPLE_PTR -#define STLPLUS_SIMPLE_PTR -//////////////////////////////////////////////////////////////////////////////// - -// Author: Daniel Milton, Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Daniel Milton, Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// A smart pointer is a memory-managing pointer to an object. If you like, it -// is a zero-dimensional container. - -// Assignment of smart pointers result in multiple aliases of the same object. -// The term alias is used to differentiate from conventional pointers because -// the semantics are different. - -// Aliases can be turned into copies if the pointed-to class supports copying. - -// These simple_ptr classes from DJDM have slightly different semantics than -// the smart_ptr classes of AJR. There are no cross-pointer side effects -// that occur when the pointer is cleared. The clear() function is effectively -// equivalent to the clear_unique() function of the smart_ptr. The only way -// that a "referenced" object will be deleted is if all simple_ptr's that -// reference the object are cleared (by deletion, manual clearing or reassignment). - -// Also, the simple pointer cannot contain a reference to a shared null pointer -// (which occurs as a side-effect of clearing a multiply referenced object in -// the smart_ptr classes). Which means that if you have a null simple_ptr, then -// the assignment of any other null simple_ptr will NOT reassign the reference of -// any other simple_ptr. Hence, the simple_ptr class acts a little more like a -// normal pointer (with fewer side effects), with the added bonus of containment. - -// Due to the way that the simple_ptr contains the data, it also allows the -// addition of various casting functions, while still keeping the managed data -// containment functionality of the underlying object. This means that you can -// have two simple_ptr's of different template types, both pointing to the same -// data (if the differing template types are derivatives of each other). - -// The base class is simple_ptr_base which defines the common interface. Then -// there are three subclasses which have the same interface but different copy -// semantics: - -// - simple_ptr for simple types and classes which have copy constructors -// - simple_ptr_clone for polymorphic class hierarchies which are copied using a clone method -// - simple_ptr_nocopy for any class that cannot or should not be copied - -//////////////////////////////////////////////////////////////////////////////// -#include "containers_fixes.hpp" -#include "exceptions.hpp" -#include "copy_functors.hpp" -#include -#include - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // Base class - //////////////////////////////////////////////////////////////////////////////// - - template - class simple_ptr_base - { - public: - ////////////////////////////////////////////////////////////////////////////// - // member type definitions - - typedef T value_type; - typedef T& reference; - typedef const T& const_reference; - typedef C value_copy; - - ////////////////////////////////////////////////////////////////////////////// - // constructors and destructors - - // create a null pointer - simple_ptr_base(void); - - // create a pointer containing a *copy* of the object using the template parameter C - // this copy is taken because the pointer class maintains a dynamically allocated object - // and the T& may not be (usually is not) dynamically allocated - explicit simple_ptr_base(const T& data) throw(illegal_copy); - - // create a pointer containing a dynamically created object - // Note: the object must be allocated *by the user* with new - // constructor form - must be called in the form smart_ptr_base x(new type(args)) - explicit simple_ptr_base(T* data); - - // copy constructor implements aliasing so no copy is made - // note that the copy constructor should NOT be explicit, as this breaks - // the returning of pointer objects from functions (at least within GCC 4.4) - simple_ptr_base(const simple_ptr_base& r); - - // assignment operator - required, else the output of GCC suffers segmentation faults - simple_ptr_base& operator=(const simple_ptr_base& r); - - // destructor decrements the reference count and delete only when the last reference is destroyed - ~simple_ptr_base(void); - - ////////////////////////////////////////////////////////////////////////////// - // logical tests to see if there is anything contained in the pointer since it can be null - - // there are two forms:explicit and implicit - // implicit: if(!r) or if(r) - // explicit: if(r.null()) or if(r.present()) - operator bool(void) const; - bool operator!(void) const; - bool present(void) const; - bool null(void) const; - - ////////////////////////////////////////////////////////////////////////////// - // dereference operators and functions - - // dereference the smart pointer to get the object - use in the form *p1 - T& operator*(void) throw(null_dereference); - const T& operator*(void) const throw(null_dereference); - - // used as a prefix to a member access to the contained object e.g. p1->print() calls T::print() - T* operator->(void) throw(null_dereference); - const T* operator->(void) const throw(null_dereference); - - ////////////////////////////////////////////////////////////////////////////// - // explicit function forms of the above assignment and dereference operators - - // set the value - note that this does a copy using the C template parameter - void set_value(const T& data) throw(illegal_copy); - // get the value - T& value(void) throw(null_dereference); - const T& value(void) const throw(null_dereference); - - // set the pointer - // deletes the previous pointer and adopts the passed pointer instead - // Note: the object must be allocated *by the user* with new - // Warning: it is very easy to break the memory management with this operation - void set(T* data = 0); - // get the pointer - T* pointer(void); - const T* pointer(void) const; - - ////////////////////////////////////////////////////////////////////////////// - // functions to manage aliases - - // make this an alias of the passed object - void alias(const simple_ptr_base&); - - // test whether two pointers point to the same object(known as aliasing the object) - // used in the form if(a.aliases(b)) - bool aliases(const simple_ptr_base&) const; - - // find the number of aliases - used when you need to know whether an - // object is still referred to from elsewhere (rare!) - unsigned alias_count(void) const; - - // clear the reference to the object, but only delete the object if there are no - // other references to that object. Hence, this does not affect other pointers - // that are pointing to the same object. - void clear(void); - - // This is just an alias of the clear() function, provided for completeness of - // the interface when acting as a replacement for the smart_ptr classes - void clear_unique(void); - - ////////////////////////////////////////////////////////////////////////////// - // functions that involve copying - - // these functions use the copy functor passed as the template parameter C - // to copy the object with the right copy semantics. If the copy functor - // is no_copy, an exception will be thrown. - - // make this pointer unique with respect to any other references to the same object - // if this pointer is already unique, it does nothing - otherwise it copies the object - void make_unique(void) throw(illegal_copy); - - // make this pointer a unique copy of the parameter - // useful for expressions like p1.copy(p2) which makes p1 a pointer to a unique copy of the contents of p2 - void copy(const simple_ptr_base&) throw(illegal_copy); - - ////////////////////////////////////////////////////////////////////////////// - // functions that involve casting - -#ifdef STLPLUS_MEMBER_TEMPLATES - - // dynamic cast of underlying pointer to a derived/parent - template simple_ptr_base dyn_cast(void) const; - - // static cast of underlying pointer to a derived/parent - template simple_ptr_base stat_cast(void) const; - - // cast of underlying pointer to a base - while keeping the same ref-counted object - template simple_ptr_base cast(void) const; - -#endif - - ////////////////////////////////////////////////////////////////////////////// - - protected: - T* m_pointer; - unsigned* m_count; - - public: - // internal use only - had to make them public because they need to be - // accessed by routines that could not be made friends - // can't have a handle due to the way the simple pointer stores it's data - // in separate counter and pointer objects - unsigned* _count(void) const; - T* _pointer(void) const; - void _make_alias(T* pointer, unsigned* count); - - private: - void increment(void); - bool decrement(void); - }; - - //////////////////////////////////////////////////////////////////////////////// - // simple_ptr for simple types and classes which have copy constructors - - template - class simple_ptr : public simple_ptr_base > - { - public: - simple_ptr(void) {} - explicit simple_ptr(const T& data) : simple_ptr_base >(data) {} - explicit simple_ptr(T* data) : simple_ptr_base >(data) {} - simple_ptr& operator=(const T& data) {set_value(data); return *this;} - simple_ptr& operator=(T* data) {set(data); return *this;} - ~simple_ptr(void) {} - }; - - //////////////////////////////////////////////////////////////////////////////// - // smart_ptr_clone for polymorphic class hierarchies which have a clone method - - template - class simple_ptr_clone : public simple_ptr_base > - { - public: - simple_ptr_clone(void) {} - explicit simple_ptr_clone(const T& data) : simple_ptr_base >(data) {} - explicit simple_ptr_clone(T* data) : simple_ptr_base >(data) {} - simple_ptr_clone& operator=(const T& data) {set_value(data); return *this;} - simple_ptr_clone& operator=(T* data) {set(data); return *this;} - ~simple_ptr_clone(void) {} - }; - - //////////////////////////////////////////////////////////////////////////////// - // smart_ptr_nocopy for any class that cannot or should not be copied - - template - class simple_ptr_nocopy : public simple_ptr_base > - { - public: - simple_ptr_nocopy(void) {} - explicit simple_ptr_nocopy(const T& data) : simple_ptr_base >(data) {} - explicit simple_ptr_nocopy(T* data) : simple_ptr_base >(data) {} - simple_ptr_nocopy& operator=(const T& data) {set_value(data); return *this;} - simple_ptr_nocopy& operator=(T* data) {set(data); return *this;} - ~simple_ptr_nocopy(void) {} - }; - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#include "simple_ptr.tpp" -#endif +#ifndef STLPLUS_SIMPLE_PTR +#define STLPLUS_SIMPLE_PTR +//////////////////////////////////////////////////////////////////////////////// + +// Author: Daniel Milton +// Copyright: (c) Daniel Milton 2002 onwards +// License: BSD License, see ../docs/license.html + +// A smart pointer is a memory-managing pointer to an object. If you like, it +// is a zero-dimensional container. + +// Assignment of smart pointers result in multiple aliases of the same object. +// The term alias is used to differentiate from conventional pointers because +// the semantics are different. + +// Aliases can be turned into copies if the pointed-to class supports copying. + +// These simple_ptr classes from DJDM have slightly different semantics than +// the smart_ptr classes of AJR. There are no cross-pointer side effects +// that occur when the pointer is cleared. The clear() function is effectively +// equivalent to the clear_unique() function of the smart_ptr. The only way +// that a "referenced" object will be deleted is if all simple_ptr's that +// reference the object are cleared (by deletion, manual clearing or reassignment). + +// Also, the simple pointer cannot contain a reference to a shared null pointer +// (which occurs as a side-effect of clearing a multiply referenced object in +// the smart_ptr classes). Which means that if you have a null simple_ptr, then +// the assignment of any other null simple_ptr will NOT reassign the reference of +// any other simple_ptr. Hence, the simple_ptr class acts a little more like a +// normal pointer (with fewer side effects), with the added bonus of containment. + +// Due to the way that the simple_ptr contains the data, it also allows the +// addition of various casting functions, while still keeping the managed data +// containment functionality of the underlying object. This means that you can +// have two simple_ptr's of different template types, both pointing to the same +// data (if the differing template types are derivatives of each other). + +// The base class is simple_ptr_base which defines the common interface. Then +// there are three subclasses which have the same interface but different copy +// semantics: + +// - simple_ptr for simple types and classes which have copy constructors +// - simple_ptr_clone for polymorphic class hierarchies which are copied using a clone method +// - simple_ptr_nocopy for any class that cannot or should not be copied + +//////////////////////////////////////////////////////////////////////////////// +#include "containers_fixes.hpp" +#include "exceptions.hpp" +#include "copy_functors.hpp" +#include +#include + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // Base class + //////////////////////////////////////////////////////////////////////////////// + + template + class simple_ptr_base + { + public: + ////////////////////////////////////////////////////////////////////////////// + // member type definitions + + typedef T value_type; + typedef T& reference; + typedef const T& const_reference; + typedef C value_copy; + + ////////////////////////////////////////////////////////////////////////////// + // constructors and destructors + + // create a null pointer + simple_ptr_base(void); + + // create a pointer containing a dynamically created object + // Note: the object must be allocated *by the user* with new + // constructor form - must be called in the form smart_ptr_base x(new type(args)) + explicit simple_ptr_base(T* data); + + // copy constructor implements aliasing so no copy is made + // note that the copy constructor should NOT be explicit, as this breaks + // the returning of pointer objects from functions (at least within GCC 4.4) + simple_ptr_base(const simple_ptr_base& r); + + // assignment operator - required, else the output of GCC suffers segmentation faults + simple_ptr_base& operator=(const simple_ptr_base& r); + + // destructor decrements the reference count and delete only when the last reference is destroyed + ~simple_ptr_base(void); + + ////////////////////////////////////////////////////////////////////////////// + // logical tests to see if there is anything contained in the pointer since it can be null + + // there are two forms:explicit and implicit + // implicit: if(!r) or if(r) + // explicit: if(r.null()) or if(r.present()) + operator bool(void) const; + bool operator!(void) const; + bool present(void) const; + bool null(void) const; + + ////////////////////////////////////////////////////////////////////////////// + // dereference operators and functions + + // dereference the smart pointer to get the object - use in the form *p1 + T& operator*(void) throw(null_dereference); + const T& operator*(void) const throw(null_dereference); + + // used as a prefix to a member access to the contained object e.g. p1->print() calls T::print() + T* operator->(void) throw(null_dereference); + const T* operator->(void) const throw(null_dereference); + + ////////////////////////////////////////////////////////////////////////////// + // explicit function forms of the above assignment and dereference operators + + // get the value + T& value(void) throw(null_dereference); + const T& value(void) const throw(null_dereference); + + // set the pointer + // deletes the previous pointer and adopts the passed pointer instead + // Note: the object must be allocated *by the user* with new + // Warning: it is very easy to break the memory management with this operation + void set(T* data = 0); + // get the pointer + T* pointer(void); + const T* pointer(void) const; + + ////////////////////////////////////////////////////////////////////////////// + // functions to manage aliases + + // make this an alias of the passed object + void alias(const simple_ptr_base&); + + // test whether two pointers point to the same object(known as aliasing the object) + // used in the form if(a.aliases(b)) + bool aliases(const simple_ptr_base&) const; + + // find the number of aliases - used when you need to know whether an + // object is still referred to from elsewhere (rare!) + unsigned alias_count(void) const; + + // clear the reference to the object, but only delete the object if there are no + // other references to that object. Hence, this does not affect other pointers + // that are pointing to the same object. + void clear(void); + + // This is just an alias of the clear() function, provided for completeness of + // the interface when acting as a replacement for the smart_ptr classes + void clear_unique(void); + + ////////////////////////////////////////////////////////////////////////////// + // functions that involve copying + + // these functions use the copy functor passed as the template parameter C + // to copy the object with the right copy semantics. If the copy functor + // is no_copy, an exception will be thrown. + + // create a pointer containing a *copy* of the object using the template parameter C + // this copy is taken because the pointer class maintains a dynamically allocated object + // and the T& may not be (usually is not) dynamically allocated + explicit simple_ptr_base(const T& data) throw(illegal_copy); + + // set the value - note that this does a copy using the C template parameter + void set_value(const T& data) throw(illegal_copy); + + // make this pointer unique with respect to any other references to the same object + // if this pointer is already unique, it does nothing - otherwise it copies the object + void make_unique(void) throw(illegal_copy); + + // make this pointer a unique copy of the parameter + // useful for expressions like p1.copy(p2) which makes p1 a pointer to a unique copy of the contents of p2 + void copy(const simple_ptr_base&) throw(illegal_copy); + + ////////////////////////////////////////////////////////////////////////////// + + protected: + T* m_pointer; + unsigned* m_count; + + public: + // internal use only - had to make them public because they need to be + // accessed by routines that could not be made friends + // can't have a handle due to the way the simple pointer stores it's data + // in separate counter and pointer objects + unsigned* _count(void) const; + T* _pointer(void) const; + void _make_alias(T* pointer, unsigned* count); + + private: + void increment(void); + bool decrement(void); + }; + + //////////////////////////////////////////////////////////////////////////////// + // simple_ptr for simple types and classes which have copy constructors + + template + class simple_ptr : public simple_ptr_base > + { + public: + simple_ptr(void) {} + explicit simple_ptr(const T& data) : simple_ptr_base >(data) {} + explicit simple_ptr(T* data) : simple_ptr_base >(data) {} + simple_ptr& operator=(const T& data) {set_value(data); return *this;} + simple_ptr& operator=(T* data) {set(data); return *this;} + ~simple_ptr(void) {} + +#ifdef STLPLUS_MEMBER_TEMPLATES + // functions that involve casting + // moved from base class for two main reasons, though the second is a feature of the first: + + // 1. GCC cannot cast the previous base result of simple_ptr_base > + // as a simple_ptr even though it used to look like a duck and quack like a duck. + // I think it was really complaining that the copy class was not guaranteed to be the same. + + // 2. Within the cast routines, one pointer type tried accessing private data of the other + // pointer type and even though they are really the same type, was not allowed. Because + // of this, the "private" function _make_alias is utilised to get the same result. + + // By having the cast functions in each derived class, you are guaranteed to use the same + // copy class - no question. GCC is ok with this. + + template simple_ptr dyn_cast(void) const; + template simple_ptr stat_cast(void) const; + template simple_ptr cast(void) const; +#endif + }; + + //////////////////////////////////////////////////////////////////////////////// + // simple_ptr_clone for polymorphic class hierarchies which have a clone method + + template + class simple_ptr_clone : public simple_ptr_base > + { + public: + simple_ptr_clone(void) {} + explicit simple_ptr_clone(const T& data) : simple_ptr_base >(data) {} + explicit simple_ptr_clone(T* data) : simple_ptr_base >(data) {} + simple_ptr_clone& operator=(const T& data) {set_value(data); return *this;} + simple_ptr_clone& operator=(T* data) {set(data); return *this;} + ~simple_ptr_clone(void) {} + +#ifdef STLPLUS_MEMBER_TEMPLATES + // functions that involve casting + // moved from base class - see simple_ptr above + template simple_ptr_clone dyn_cast(void) const; + template simple_ptr_clone stat_cast(void) const; + template simple_ptr_clone cast(void) const; +#endif +}; + + //////////////////////////////////////////////////////////////////////////////// + // simple_ptr_nocopy for any class that cannot or should not be copied + + template + class simple_ptr_nocopy : public simple_ptr_base > + { + public: + simple_ptr_nocopy(void) {} + explicit simple_ptr_nocopy(T* data) : simple_ptr_base >(data) {} + simple_ptr_nocopy& operator=(T* data) {set(data); return *this;} + ~simple_ptr_nocopy(void) {} + +#ifdef STLPLUS_MEMBER_TEMPLATES + // functions that involve casting + // moved from base class - see simple_ptr above + template simple_ptr_nocopy dyn_cast(void) const; + template simple_ptr_nocopy stat_cast(void) const; + template simple_ptr_nocopy cast(void) const; +#endif + }; + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#include "simple_ptr.tpp" +#endif diff --git a/src/stlplus/containers/simple_ptr.tpp b/src/stlplus/containers/simple_ptr.tpp index ead6744..4b08ffe 100644 --- a/src/stlplus/containers/simple_ptr.tpp +++ b/src/stlplus/containers/simple_ptr.tpp @@ -1,338 +1,395 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Daniel Milton -// Copyright: (c) Daniel Milton 2002-2009 - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // simple_ptr_base class - //////////////////////////////////////////////////////////////////////////////// - - //////////////////////////////////////////////////////////////////////////////// - // constructors, assignments and destructors - - // create a null pointer - template - simple_ptr_base::simple_ptr_base(void) : - m_pointer(0), - m_count(new unsigned(1)) - { - } - - // create a pointer containing a *copy* of the object pointer - template - simple_ptr_base::simple_ptr_base(const T& data) throw(illegal_copy) : - m_pointer(C()(data)), - m_count(new unsigned(1)) - { - } - - // create a pointer containing a dynamically created object - // Note: the object must be allocated *by the user* with new - // constructor form - must be called in the form simple_ptr x(new type(args)) - template - simple_ptr_base::simple_ptr_base(T* data) : - m_pointer(data), - m_count(new unsigned(1)) - { - } - - // copy constructor implements counted referencing - no copy is made - template - simple_ptr_base::simple_ptr_base(const simple_ptr_base& r) : - m_pointer(r.m_pointer), - m_count(r.m_count) - { - increment(); - } - - // assignment operator - required, else the output of GCC suffers segmentation faults - template - simple_ptr_base& simple_ptr_base::operator=(const simple_ptr_base& r) - { - alias(r); - return *this; - } - - // destructor decrements the reference count and delete only when the last reference is destroyed - template - simple_ptr_base::~simple_ptr_base(void) - { - if(decrement()) - { - delete m_pointer; - delete m_count; - } - } - - ////////////////////////////////////////////////////////////////////////////// - // logical tests to see if there is anything contained in the pointer since it can be null - - template - bool simple_ptr_base::null(void) const - { - return m_pointer==0; - } - - template - bool simple_ptr_base::present(void) const - { - return m_pointer!=0; - } - - template - bool simple_ptr_base::operator!(void) const - { - return m_pointer==0; - } - - template - simple_ptr_base::operator bool(void) const - { - return m_pointer!=0; - } - - ////////////////////////////////////////////////////////////////////////////// - // dereference operators and functions - - template - T& simple_ptr_base::operator*(void) throw(null_dereference) - { - if (!m_pointer) throw null_dereference("null pointer dereferenced in simple_ptr::operator*"); - return *m_pointer; - } - - template - const T& simple_ptr_base::operator*(void) const throw(null_dereference) - { - if (!m_pointer) throw null_dereference("null pointer dereferenced in simple_ptr::operator*"); - return *m_pointer; - } - - template - T* simple_ptr_base::operator->(void) throw(null_dereference) - { - if (!m_pointer) throw null_dereference("null pointer dereferenced in simple_ptr::operator->"); - return m_pointer; - } - - template - const T* simple_ptr_base::operator->(void) const throw(null_dereference) - { - if (!m_pointer) throw null_dereference("null pointer dereferenced in simple_ptr::operator->"); - return m_pointer; - } - - ////////////////////////////////////////////////////////////////////////////// - // explicit function forms of the above assignment dereference operators - - template - void simple_ptr_base::set_value(const T& data) throw(illegal_copy) - { - set(C()(data)); - } - - template - T& simple_ptr_base::value(void) throw(null_dereference) - { - if (!m_pointer) throw null_dereference("null pointer dereferenced in simple_ptr::value"); - return *m_pointer; - } - - template - const T& simple_ptr_base::value(void) const throw(null_dereference) - { - if (!m_pointer) throw null_dereference("null pointer dereferenced in simple_ptr::value"); - return *m_pointer; - } - - template - void simple_ptr_base::set(T* data) - { - unsigned& count = *m_count; - if (count<=1) - delete m_pointer; - else - { - --count; - m_count = new unsigned(1); - } - m_pointer = data; - } - - template - T* simple_ptr_base::pointer(void) - { - return m_pointer; - } - - template - const T* simple_ptr_base::pointer(void) const - { - return m_pointer; - } - - //////////////////////////////////////////////////////////////////////////////// - // functions to manage counted referencing - - template - void simple_ptr_base::increment(void) - { - ++(*m_count); - } - - template - bool simple_ptr_base::decrement(void) - { - unsigned& count = *m_count; - --count; - return count == 0; - } - - // make this an alias of the passed object - template - void simple_ptr_base::alias(const simple_ptr_base& r) - { - // make it alias-copy safe - this means that I don't try to do the - // assignment if r is either the same object or an alias of it - if (m_pointer==r.m_pointer) return; - if(decrement()) { - delete m_pointer; - delete m_count; - } - m_pointer = r.m_pointer; - m_count = r.m_count; - increment(); - } - - template - bool simple_ptr_base::aliases(const simple_ptr_base& r) const - { - return m_count == r.m_count; - } - - template - unsigned simple_ptr_base::alias_count(void) const - { - return *m_count; - } - - template - void simple_ptr_base::clear(void) - { - set(0); - } - - template - void simple_ptr_base::clear_unique(void) - { - set(0); // no difference between clear and clear_unique with the simple_ptr - } - - template - void simple_ptr_base::make_unique(void) throw(illegal_copy) - { - unsigned& count = *m_count; - if (count <= 1) return; - --count; - if (m_pointer) m_pointer = C()(*m_pointer); - m_count = new unsigned(1); - } - - template - void simple_ptr_base::copy(const simple_ptr_base& data) throw(illegal_copy) - { - alias(data); - make_unique(); - } - -#ifdef STLPLUS_MEMBER_TEMPLATES - - // dynamic cast of underlying pointer to a derived/parent - template - template - simple_ptr_base simple_ptr_base::dyn_cast(void) const - { - simple_ptr_base rtn; - rtn.m_pointer = dynamic_cast(m_pointer); - if (rtn.m_pointer) { - delete rtn.m_count; - rtn.m_count = m_count; - rtn.increment(); - } - return rtn; - } - - // static cast of underlying pointer to a derived/parent - template - template - simple_ptr_base simple_ptr_base::stat_cast(void) const - { - simple_ptr_base rtn; - rtn.m_pointer = static_cast(m_pointer); - if (rtn.m_pointer) { - delete rtn.m_count; - rtn.m_count = m_count; - rtn.increment(); - } - return rtn; - } - - // cast of underlying pointer to a base - while keeping the same ref-counted object - template - template - simple_ptr_base simple_ptr_base::cast(void) const - { - simple_ptr_base rtn; - rtn.m_pointer = (T2*)m_pointer; - if (rtn.m_pointer) { - delete rtn.m_count; - rtn.m_count = m_count; - rtn.increment(); - } - return rtn; - } - -#endif - - // internal function for distinguishing unique simple_ptr objects - // used for example in persistence routines - - template - unsigned* simple_ptr_base::_count(void) const - { - return m_count; - } - - template - T* simple_ptr_base::_pointer(void) const - { - return m_pointer; - } - - template - void simple_ptr_base::_make_alias(T* pointer, unsigned* count) - { - // make it alias-copy safe - this means that I don't try to do the - // assignment if r is either the same object or an alias of it - if (m_count != count) - { - if(decrement()) - { - delete m_pointer; - delete m_count; - } - m_pointer = pointer; - m_count = count; - increment(); - } - } - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - +//////////////////////////////////////////////////////////////////////////////// + +// Author: Daniel Milton +// Copyright: (c) Daniel Milton 2002 onwards + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // simple_ptr_base class + //////////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////////// + // constructors, assignments and destructors + + // create a null pointer + template + simple_ptr_base::simple_ptr_base(void) : + m_pointer(0), + m_count(new unsigned(1)) + { + } + + // create a pointer containing a *copy* of the object pointer + template + simple_ptr_base::simple_ptr_base(const T& data) throw(illegal_copy) : + m_pointer(C()(data)), + m_count(new unsigned(1)) + { + } + + // create a pointer containing a dynamically created object + // Note: the object must be allocated *by the user* with new + // constructor form - must be called in the form simple_ptr x(new type(args)) + template + simple_ptr_base::simple_ptr_base(T* data) : + m_pointer(data), + m_count(new unsigned(1)) + { + } + + // copy constructor implements counted referencing - no copy is made + template + simple_ptr_base::simple_ptr_base(const simple_ptr_base& r) : + m_pointer(r.m_pointer), + m_count(r.m_count) + { + increment(); + } + + // assignment operator - required, else the output of GCC suffers segmentation faults + template + simple_ptr_base& simple_ptr_base::operator=(const simple_ptr_base& r) + { + alias(r); + return *this; + } + + // destructor decrements the reference count and delete only when the last reference is destroyed + template + simple_ptr_base::~simple_ptr_base(void) + { + if(decrement()) + { + delete m_pointer; + delete m_count; + } + } + + ////////////////////////////////////////////////////////////////////////////// + // logical tests to see if there is anything contained in the pointer since it can be null + + template + bool simple_ptr_base::null(void) const + { + return m_pointer==0; + } + + template + bool simple_ptr_base::present(void) const + { + return m_pointer!=0; + } + + template + bool simple_ptr_base::operator!(void) const + { + return m_pointer==0; + } + + template + simple_ptr_base::operator bool(void) const + { + return m_pointer!=0; + } + + ////////////////////////////////////////////////////////////////////////////// + // dereference operators and functions + + template + T& simple_ptr_base::operator*(void) throw(null_dereference) + { + if (!m_pointer) throw null_dereference("null pointer dereferenced in simple_ptr::operator*"); + return *m_pointer; + } + + template + const T& simple_ptr_base::operator*(void) const throw(null_dereference) + { + if (!m_pointer) throw null_dereference("null pointer dereferenced in simple_ptr::operator*"); + return *m_pointer; + } + + template + T* simple_ptr_base::operator->(void) throw(null_dereference) + { + if (!m_pointer) throw null_dereference("null pointer dereferenced in simple_ptr::operator->"); + return m_pointer; + } + + template + const T* simple_ptr_base::operator->(void) const throw(null_dereference) + { + if (!m_pointer) throw null_dereference("null pointer dereferenced in simple_ptr::operator->"); + return m_pointer; + } + + ////////////////////////////////////////////////////////////////////////////// + // explicit function forms of the above assignment dereference operators + + template + void simple_ptr_base::set_value(const T& data) throw(illegal_copy) + { + set(C()(data)); + } + + template + T& simple_ptr_base::value(void) throw(null_dereference) + { + if (!m_pointer) throw null_dereference("null pointer dereferenced in simple_ptr::value"); + return *m_pointer; + } + + template + const T& simple_ptr_base::value(void) const throw(null_dereference) + { + if (!m_pointer) throw null_dereference("null pointer dereferenced in simple_ptr::value"); + return *m_pointer; + } + + template + void simple_ptr_base::set(T* data) + { + unsigned& count = *m_count; + if (count<=1) + delete m_pointer; + else + { + --count; + m_count = new unsigned(1); + } + m_pointer = data; + } + + template + T* simple_ptr_base::pointer(void) + { + return m_pointer; + } + + template + const T* simple_ptr_base::pointer(void) const + { + return m_pointer; + } + + //////////////////////////////////////////////////////////////////////////////// + // functions to manage counted referencing + + template + void simple_ptr_base::increment(void) + { + ++(*m_count); + } + + template + bool simple_ptr_base::decrement(void) + { + unsigned& count = *m_count; + --count; + return count == 0; + } + + // make this an alias of the passed object + template + void simple_ptr_base::alias(const simple_ptr_base& r) + { + // make it alias-copy safe - this means that I don't try to do the + // assignment if r is either the same object or an alias of it + if (m_pointer==r.m_pointer) return; + if(decrement()) { + delete m_pointer; + delete m_count; + } + m_pointer = r.m_pointer; + m_count = r.m_count; + increment(); + } + + template + bool simple_ptr_base::aliases(const simple_ptr_base& r) const + { + return m_count == r.m_count; + } + + template + unsigned simple_ptr_base::alias_count(void) const + { + return *m_count; + } + + template + void simple_ptr_base::clear(void) + { + set(0); + } + + template + void simple_ptr_base::clear_unique(void) + { + set(0); // no difference between clear and clear_unique with the simple_ptr + } + + template + void simple_ptr_base::make_unique(void) throw(illegal_copy) + { + unsigned& count = *m_count; + if (count <= 1) return; + --count; + if (m_pointer) m_pointer = C()(*m_pointer); + m_count = new unsigned(1); + } + + template + void simple_ptr_base::copy(const simple_ptr_base& data) throw(illegal_copy) + { + alias(data); + make_unique(); + } + + // internal function for distinguishing unique simple_ptr objects + // used for example in persistence routines + + template + unsigned* simple_ptr_base::_count(void) const + { + return m_count; + } + + template + T* simple_ptr_base::_pointer(void) const + { + return m_pointer; + } + + template + void simple_ptr_base::_make_alias(T* pointer, unsigned* count) + { + // make it alias-copy safe - this means that I don't try to do the + // assignment if r is either the same object or an alias of it + if (m_count != count) + { + if(decrement()) + { + delete m_pointer; + delete m_count; + } + m_pointer = pointer; + m_count = count; + increment(); + } + } + + + //////////////////////////////////////////////////////////////////////////////// + // simple_ptr class + //////////////////////////////////////////////////////////////////////////////// + +#ifdef STLPLUS_MEMBER_TEMPLATES + + template + template + simple_ptr simple_ptr::dyn_cast(void) const { + simple_ptr rtn; + T2* p = dynamic_cast(this->m_pointer); + if (p) rtn._make_alias(p, this->m_count); + return rtn; + } + + template + template + simple_ptr simple_ptr::stat_cast(void) const { + simple_ptr rtn; + T2* p = static_cast(this->m_pointer); + if (p) rtn._make_alias(p, this->m_count); + return rtn; + } + + template + template + simple_ptr simple_ptr::cast(void) const { + simple_ptr rtn; + T2* p = (T2*)this->m_pointer; + if (p) rtn._make_alias(p, this->m_count); + return rtn; + } + +#endif + + //////////////////////////////////////////////////////////////////////////////// + // simple_ptr_clone class + //////////////////////////////////////////////////////////////////////////////// + +#ifdef STLPLUS_MEMBER_TEMPLATES + + template + template + simple_ptr_clone simple_ptr_clone::dyn_cast(void) const { + simple_ptr_clone rtn; + T2* p = dynamic_cast(this->m_pointer); + if (p) rtn._make_alias(p, this->m_count); + return rtn; + } + + template + template + simple_ptr_clone simple_ptr_clone::stat_cast(void) const { + simple_ptr_clone rtn; + T2* p = static_cast(this->m_pointer); + if (p) rtn._make_alias(p, this->m_count); + return rtn; + } + + template + template + simple_ptr_clone simple_ptr_clone::cast(void) const { + simple_ptr_clone rtn; + T2* p = (T2*)this->m_pointer; + if (p) rtn._make_alias(p, this->m_count); + return rtn; + } + +#endif + + //////////////////////////////////////////////////////////////////////////////// + // simple_ptr_nocopy class + //////////////////////////////////////////////////////////////////////////////// + +#ifdef STLPLUS_MEMBER_TEMPLATES + + template + template + simple_ptr_nocopy simple_ptr_nocopy::dyn_cast(void) const { + simple_ptr_nocopy rtn; + T2* p = dynamic_cast(this->m_pointer); + if (p) rtn._make_alias(p, this->m_count); + return rtn; + } + + template + template + simple_ptr_nocopy simple_ptr_nocopy::stat_cast(void) const { + simple_ptr_nocopy rtn; + T2* p = static_cast(this->m_pointer); + if (p) rtn._make_alias(p, this->m_count); + return rtn; + } + + template + template + simple_ptr_nocopy simple_ptr_nocopy::cast(void) const { + simple_ptr_nocopy rtn; + T2* p = (T2*)this->m_pointer; + if (p) rtn._make_alias(p, this->m_count); + return rtn; + } + +#endif + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + diff --git a/src/stlplus/containers/smart_ptr.hpp b/src/stlplus/containers/smart_ptr.hpp index 0170771..18b496e 100644 --- a/src/stlplus/containers/smart_ptr.hpp +++ b/src/stlplus/containers/smart_ptr.hpp @@ -1,222 +1,221 @@ -#ifndef STLPLUS_SMART_PTR -#define STLPLUS_SMART_PTR -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// A smart pointer is a memory-managing pointer to an object. If you like, it -// is a zero-dimensional container. - -// Assignment of smart pointers result in multiple aliases of the same object. -// The term alias is used to differentiate from conventional pointers because -// the semantics are different. - -// Aliases can be turned into copies if the pointed-to class supports copying. - -// The base class is smart_ptr_base which defines the common interface. Then -// there are three subclasses which have the same interface but different copy -// semantics: - -// - smart_ptr for simple types and classes which have copy constructors -// - smart_ptr_clone for polymorphic class hierarchies which are copied using a clone method -// - smart_ptr_nocopy for any class that cannot or should not be copied - -//////////////////////////////////////////////////////////////////////////////// -#include "containers_fixes.hpp" -#include "exceptions.hpp" -#include "copy_functors.hpp" -#include -#include - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // internals - - template class smart_ptr_holder; - - //////////////////////////////////////////////////////////////////////////////// - // Base class - //////////////////////////////////////////////////////////////////////////////// - - template - class smart_ptr_base - { - public: - ////////////////////////////////////////////////////////////////////////////// - // member type definitions - - typedef T value_type; - typedef T& reference; - typedef const T& const_reference; - typedef C value_copy; - - ////////////////////////////////////////////////////////////////////////////// - // constructors and destructors - - // create a null pointer - smart_ptr_base(void); - - // create a pointer containing a *copy* of the object using the template parameter C - // this copy is taken because the pointer class maintains a dynamically allocated object - // and the T& may not be (usually is not) dynamically allocated - explicit smart_ptr_base(const T& data) throw(illegal_copy); - - // create a pointer containing a dynamically created object - // Note: the object must be allocated *by the user* with new - // constructor form - must be called in the form smart_ptr_base x(new type(args)) - explicit smart_ptr_base(T* data); - - // copy constructor implements aliasing so no copy is made - // note that the copy constructor should NOT be explicit, as this breaks - // the returning of pointer objects from functions (at least within GCC 4.4) - smart_ptr_base(const smart_ptr_base& r); - - // assignment operator - required, else the output of GCC suffers segmentation faults - smart_ptr_base& operator=(const smart_ptr_base& r); - - // destructor decrements the reference count and delete only when the last reference is destroyed - ~smart_ptr_base(void); - - ////////////////////////////////////////////////////////////////////////////// - // logical tests to see if there is anything contained in the pointer since it can be null - - // there are two forms:explicit and implicit - // implicit: if(!r) or if(r) - // explicit: if(r.null()) or if(r.present()) - operator bool(void) const; - bool operator!(void) const; - bool present(void) const; - bool null(void) const; - - ////////////////////////////////////////////////////////////////////////////// - // dereference operators and functions - - // dereference the smart pointer to get the object - use in the form *p1 - T& operator*(void) throw(null_dereference); - const T& operator*(void) const throw(null_dereference); - - // used as a prefix to a member access to the contained object e.g. p1->print() calls T::print() - T* operator->(void) throw(null_dereference); - const T* operator->(void) const throw(null_dereference); - - ////////////////////////////////////////////////////////////////////////////// - // explicit function forms of the above assignment and dereference operators - - // set the value - note that this does a copy using the C template parameter - void set_value(const T& data) throw(illegal_copy); - // get the value - T& value(void) throw(null_dereference); - const T& value(void) const throw(null_dereference); - - // set the pointer - // deletes the previous pointer and adopts the passed pointer instead - // Note: the object must be allocated *by the user* with new - // Warning: it is very easy to break the memory management with this operation - void set(T* data = 0); - // get the pointer - T* pointer(void); - const T* pointer(void) const; - - ////////////////////////////////////////////////////////////////////////////// - // functions to manage aliases - - // make this an alias of the passed object - void alias(const smart_ptr_base&); - - // test whether two pointers point to the same object(known as aliasing the object) - // used in the form if(a.aliases(b)) - bool aliases(const smart_ptr_base&) const; - - // find the number of aliases - used when you need to know whether an - // object is still referred to from elsewhere (rare!) - unsigned alias_count(void) const; - - // delete the object and make the pointer null - does not make it unique - // first, so all other pointers to this will be null too - void clear(void); - - // make the pointer unique and null in one step - does not affect other - // pointers that were pointing to the same object - void clear_unique(void); - - ////////////////////////////////////////////////////////////////////////////// - // functions that involve copying - - // these functions use the copy functor passed as the template parameter C - // to copy the object with the right copy semantics. If the copy functor - // is no_copy, an exception will be thrown. - - // make this pointer unique with respect to any other references to the same object - // if this pointer is already unique, it does nothing - otherwise it copies the object - void make_unique(void) throw(illegal_copy); - - // make this pointer a unique copy of the parameter - // useful for expressions like p1.copy(p2) which makes p1 a pointer to a unique copy of the contents of p2 - void copy(const smart_ptr_base&) throw(illegal_copy); - - protected: - smart_ptr_holder* m_holder; - - public: - // internal use only - had to make them public because they need to be - // accessed by routines that could not be made friends - smart_ptr_holder* _handle(void) const; - void _make_alias(smart_ptr_holder* handle); - }; - - //////////////////////////////////////////////////////////////////////////////// - // smart_ptr for simple types and classes which have copy constructors - - template - class smart_ptr : public smart_ptr_base > - { - public: - smart_ptr(void) {} - explicit smart_ptr(const T& data) : smart_ptr_base >(data) {} - explicit smart_ptr(T* data) : smart_ptr_base >(data) {} - smart_ptr& operator=(const T& data) {set_value(data); return *this;} - smart_ptr& operator=(T* data) {set(data); return *this;} - ~smart_ptr(void) {} - }; - - //////////////////////////////////////////////////////////////////////////////// - // smart_ptr_clone for polymorphic class hierarchies which have a clone method - - template - class smart_ptr_clone : public smart_ptr_base > - { - public: - smart_ptr_clone(void) {} - explicit smart_ptr_clone(const T& data) : smart_ptr_base >(data) {} - explicit smart_ptr_clone(T* data) : smart_ptr_base >(data) {} - smart_ptr_clone& operator=(const T& data) {set_value(data); return *this;} - smart_ptr_clone& operator=(T* data) {set(data); return *this;} - ~smart_ptr_clone(void) {} - }; - - //////////////////////////////////////////////////////////////////////////////// - // smart_ptr_nocopy for any class that cannot or should not be copied - - template - class smart_ptr_nocopy : public smart_ptr_base > - { - public: - smart_ptr_nocopy(void) {} - explicit smart_ptr_nocopy(const T& data) : smart_ptr_base >(data) {} - explicit smart_ptr_nocopy(T* data) : smart_ptr_base >(data) {} - smart_ptr_nocopy& operator=(const T& data) {set_value(data); return *this;} - smart_ptr_nocopy& operator=(T* data) {set(data); return *this;} - ~smart_ptr_nocopy(void) {} - }; - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#include "smart_ptr.tpp" -#endif +#ifndef STLPLUS_SMART_PTR +#define STLPLUS_SMART_PTR +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// A smart pointer is a memory-managing pointer to an object. If you like, it +// is a zero-dimensional container. + +// Assignment of smart pointers result in multiple aliases of the same object. +// The term alias is used to differentiate from conventional pointers because +// the semantics are different. + +// Aliases can be turned into copies if the pointed-to class supports copying. + +// The base class is smart_ptr_base which defines the common interface. Then +// there are three subclasses which have the same interface but different copy +// semantics: + +// - smart_ptr for simple types and classes which have copy constructors +// - smart_ptr_clone for polymorphic class hierarchies which are copied using a clone method +// - smart_ptr_nocopy for any class that cannot or should not be copied + +//////////////////////////////////////////////////////////////////////////////// +#include "containers_fixes.hpp" +#include "exceptions.hpp" +#include "copy_functors.hpp" +#include +#include + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // internals + + template class smart_ptr_holder; + + //////////////////////////////////////////////////////////////////////////////// + // Base class + //////////////////////////////////////////////////////////////////////////////// + + template + class smart_ptr_base + { + public: + ////////////////////////////////////////////////////////////////////////////// + // member type definitions + + typedef T value_type; + typedef T& reference; + typedef const T& const_reference; + typedef C value_copy; + + ////////////////////////////////////////////////////////////////////////////// + // constructors and destructors + + // create a null pointer + smart_ptr_base(void); + + // create a pointer containing a dynamically created object + // Note: the object must be allocated *by the user* with new + // constructor form - must be called in the form smart_ptr_base x(new type(args)) + explicit smart_ptr_base(T* data); + + // copy constructor implements aliasing so no copy is made + // note that the copy constructor should NOT be explicit, as this breaks + // the returning of pointer objects from functions (at least within GCC 4.4) + smart_ptr_base(const smart_ptr_base& r); + + // assignment operator - required, else the output of GCC suffers segmentation faults + smart_ptr_base& operator=(const smart_ptr_base& r); + + // destructor decrements the reference count and delete only when the last reference is destroyed + ~smart_ptr_base(void); + + ////////////////////////////////////////////////////////////////////////////// + // logical tests to see if there is anything contained in the pointer since it can be null + + // there are two forms:explicit and implicit + // implicit: if(!r) or if(r) + // explicit: if(r.null()) or if(r.present()) + operator bool(void) const; + bool operator!(void) const; + bool present(void) const; + bool null(void) const; + + ////////////////////////////////////////////////////////////////////////////// + // dereference operators and functions + + // dereference the smart pointer to get the object - use in the form *p1 + T& operator*(void) throw(null_dereference); + const T& operator*(void) const throw(null_dereference); + + // used as a prefix to a member access to the contained object e.g. p1->print() calls T::print() + T* operator->(void) throw(null_dereference); + const T* operator->(void) const throw(null_dereference); + + ////////////////////////////////////////////////////////////////////////////// + // explicit function forms of the above assignment and dereference operators + + // get the value + T& value(void) throw(null_dereference); + const T& value(void) const throw(null_dereference); + + // set the pointer + // deletes the previous pointer and adopts the passed pointer instead + // Note: the object must be allocated *by the user* with new + // Warning: it is very easy to break the memory management with this operation + void set(T* data = 0); + // get the pointer + T* pointer(void); + const T* pointer(void) const; + + ////////////////////////////////////////////////////////////////////////////// + // functions to manage aliases + + // make this an alias of the passed object + void alias(const smart_ptr_base&); + + // test whether two pointers point to the same object(known as aliasing the object) + // used in the form if(a.aliases(b)) + bool aliases(const smart_ptr_base&) const; + + // find the number of aliases - used when you need to know whether an + // object is still referred to from elsewhere (rare!) + unsigned alias_count(void) const; + + // delete the object and make the pointer null - does not make it unique + // first, so all other pointers to this will be null too + void clear(void); + + // make the pointer unique and null in one step - does not affect other + // pointers that were pointing to the same object + void clear_unique(void); + + ////////////////////////////////////////////////////////////////////////////// + // functions that involve copying + + // these functions use the copy functor passed as the template parameter C + // to copy the object with the right copy semantics. If the copy functor + // is no_copy, an exception will be thrown. + + // create a pointer containing a *copy* of the object using the template parameter C + // this copy is taken because the pointer class maintains a dynamically allocated object + // and the T& may not be (usually is not) dynamically allocated + explicit smart_ptr_base(const T& data) throw(illegal_copy); + + // set the value - note that this does a copy using the C template parameter + void set_value(const T& data) throw(illegal_copy); + + // make this pointer unique with respect to any other references to the same object + // if this pointer is already unique, it does nothing - otherwise it copies the object + void make_unique(void) throw(illegal_copy); + + // make this pointer a unique copy of the parameter + // useful for expressions like p1.copy(p2) which makes p1 a pointer to a unique copy of the contents of p2 + void copy(const smart_ptr_base&) throw(illegal_copy); + + protected: + smart_ptr_holder* m_holder; + + public: + // internal use only - had to make them public because they need to be + // accessed by routines that could not be made friends + smart_ptr_holder* _handle(void) const; + void _make_alias(smart_ptr_holder* handle); + }; + + //////////////////////////////////////////////////////////////////////////////// + // smart_ptr for simple types and classes which have copy constructors + + template + class smart_ptr : public smart_ptr_base > + { + public: + smart_ptr(void) {} + explicit smart_ptr(const T& data) : smart_ptr_base >(data) {} + explicit smart_ptr(T* data) : smart_ptr_base >(data) {} + smart_ptr& operator=(const T& data) {set_value(data); return *this;} + smart_ptr& operator=(T* data) {set(data); return *this;} + ~smart_ptr(void) {} + }; + + //////////////////////////////////////////////////////////////////////////////// + // smart_ptr_clone for polymorphic class hierarchies which have a clone method + + template + class smart_ptr_clone : public smart_ptr_base > + { + public: + smart_ptr_clone(void) {} + explicit smart_ptr_clone(const T& data) : smart_ptr_base >(data) {} + explicit smart_ptr_clone(T* data) : smart_ptr_base >(data) {} + smart_ptr_clone& operator=(const T& data) {set_value(data); return *this;} + smart_ptr_clone& operator=(T* data) {set(data); return *this;} + ~smart_ptr_clone(void) {} + }; + + //////////////////////////////////////////////////////////////////////////////// + // smart_ptr_nocopy for any class that cannot or should not be copied + + template + class smart_ptr_nocopy : public smart_ptr_base > + { + public: + smart_ptr_nocopy(void) {} + explicit smart_ptr_nocopy(T* data) : smart_ptr_base >(data) {} + smart_ptr_nocopy& operator=(T* data) {set(data); return *this;} + ~smart_ptr_nocopy(void) {} + }; + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#include "smart_ptr.tpp" +#endif diff --git a/src/stlplus/containers/smart_ptr.tpp b/src/stlplus/containers/smart_ptr.tpp index 3af0210..880b69a 100644 --- a/src/stlplus/containers/smart_ptr.tpp +++ b/src/stlplus/containers/smart_ptr.tpp @@ -1,345 +1,345 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // internal holder data structure - //////////////////////////////////////////////////////////////////////////////// - - template - class smart_ptr_holder - { - private: - unsigned m_count; - T* m_data; - - // make these private to disallow copying because the holder doesn't know how to copy - smart_ptr_holder(const smart_ptr_holder& s) : - m_count(0), m_data(0) - { - } - - smart_ptr_holder& operator=(const smart_ptr_holder& s) - { - return *this; - } - - public: - smart_ptr_holder(T* p = 0) : - m_count(1), m_data(p) - { - } - - ~smart_ptr_holder(void) - { - clear(); - } - - unsigned count(void) const - { - return m_count; - } - - void increment(void) - { - ++m_count; - } - - bool decrement(void) - { - --m_count; - return m_count == 0; - } - - bool null(void) - { - return m_data == 0; - } - - void clear(void) - { - if(m_data) - delete m_data; - m_data = 0; - } - - void set(T* p = 0) - { - clear(); - m_data = p; - } - - T*& pointer(void) - { - return m_data; - } - - const T* pointer(void) const - { - return m_data; - } - - T& value(void) - { - return *m_data; - } - - const T& value(void) const - { - return *m_data; - } - }; - - //////////////////////////////////////////////////////////////////////////////// - // smart_ptr_base class - //////////////////////////////////////////////////////////////////////////////// - - //////////////////////////////////////////////////////////////////////////////// - // constructors, assignments and destructors - - // create a null pointer - template - smart_ptr_base::smart_ptr_base(void) : - m_holder(new smart_ptr_holder) - { - } - - // create a pointer containing a *copy* of the object pointer - template - smart_ptr_base::smart_ptr_base(const T& data) throw(illegal_copy) : - m_holder(new smart_ptr_holder) - { - m_holder->set(C()(data)); - } - - // create a pointer containing a dynamically created object - // Note: the object must be allocated *by the user* with new - // constructor form - must be called in the form smart_ptr x(new type(args)) - template - smart_ptr_base::smart_ptr_base(T* data) : - m_holder(new smart_ptr_holder) - { - m_holder->set(data); - } - - // copy constructor implements counted referencing - no copy is made - template - smart_ptr_base::smart_ptr_base(const smart_ptr_base& r) : - m_holder(0) - { - m_holder = r.m_holder; - m_holder->increment(); - } - - // assignment operator - required, else the output of GCC suffers segmentation faults - template - smart_ptr_base& smart_ptr_base::operator=(const smart_ptr_base& r) - { - alias(r); - return *this; - } - - // destructor decrements the reference count and delete only when the last reference is destroyed - template - smart_ptr_base::~smart_ptr_base(void) - { - if(m_holder->decrement()) - delete m_holder; - } - - ////////////////////////////////////////////////////////////////////////////// - // logical tests to see if there is anything contained in the pointer since it can be null - - template - bool smart_ptr_base::null(void) const - { - return m_holder->null(); - } - - template - bool smart_ptr_base::present(void) const - { - return !m_holder->null(); - } - - template - bool smart_ptr_base::operator!(void) const - { - return m_holder->null(); - } - - template - smart_ptr_base::operator bool(void) const - { - return !m_holder->null(); - } - - ////////////////////////////////////////////////////////////////////////////// - // dereference operators and functions - - template - T& smart_ptr_base::operator*(void) throw(null_dereference) - { - if (m_holder->null()) throw null_dereference("null pointer dereferenced in smart_ptr::operator*"); - return m_holder->value(); - } - - template - const T& smart_ptr_base::operator*(void) const throw(null_dereference) - { - if (m_holder->null()) throw null_dereference("null pointer dereferenced in smart_ptr::operator*"); - return m_holder->value(); - } - - template - T* smart_ptr_base::operator->(void) throw(null_dereference) - { - if (m_holder->null()) throw null_dereference("null pointer dereferenced in smart_ptr::operator->"); - return m_holder->pointer(); - } - - template - const T* smart_ptr_base::operator->(void) const throw(null_dereference) - { - if (m_holder->null()) throw null_dereference("null pointer dereferenced in smart_ptr::operator->"); - return m_holder->pointer(); - } - - ////////////////////////////////////////////////////////////////////////////// - // explicit function forms of the above assignment dereference operators - - template - void smart_ptr_base::set_value(const T& data) throw(illegal_copy) - { - m_holder->set(C()(data)); - } - - template - T& smart_ptr_base::value(void) throw(null_dereference) - { - if (m_holder->null()) throw null_dereference("null pointer dereferenced in smart_ptr::value"); - return m_holder->value(); - } - - template - const T& smart_ptr_base::value(void) const throw(null_dereference) - { - if (m_holder->null()) throw null_dereference("null pointer dereferenced in smart_ptr::value"); - return m_holder->value(); - } - - template - void smart_ptr_base::set(T* data) - { - m_holder->set(data); - } - - template - T* smart_ptr_base::pointer(void) - { - return m_holder->pointer(); - } - - template - const T* smart_ptr_base::pointer(void) const - { - return m_holder->pointer(); - } - - //////////////////////////////////////////////////////////////////////////////// - // functions to manage counted referencing - - // make this an alias of the passed object - template - void smart_ptr_base::alias(const smart_ptr_base& r) - { - _make_alias(r.m_holder); - } - - template - bool smart_ptr_base::aliases(const smart_ptr_base& r) const - { - return m_holder == r.m_holder; - } - - template - unsigned smart_ptr_base::alias_count(void) const - { - return m_holder->count(); - } - - template - void smart_ptr_base::clear(void) - { - m_holder->clear(); - } - - template - void smart_ptr_base::clear_unique(void) - { - if (m_holder->count() == 1) - m_holder->clear(); - else - { - m_holder->decrement(); - m_holder = 0; - m_holder = new smart_ptr_holder; - } - } - - template - void smart_ptr_base::make_unique(void) throw(illegal_copy) - { - if (m_holder->count() > 1) - { - smart_ptr_holder* old_holder = m_holder; - m_holder->decrement(); - m_holder = 0; - m_holder = new smart_ptr_holder; - if (old_holder->pointer()) - m_holder->set(C()(old_holder->value())); - } - } - - template - void smart_ptr_base::copy(const smart_ptr_base& data) throw(illegal_copy) - { - alias(data); - make_unique(); - } - - // internal function for distinguishing unique smart_ptr objects - // used for example in persistence routines - - template - smart_ptr_holder* smart_ptr_base::_handle(void) const - { - return m_holder; - } - - template - void smart_ptr_base::_make_alias(smart_ptr_holder* r_holder) - { - // make it alias-copy safe - this means that I don't try to do the - // assignment if r is either the same object or an alias of it - if (m_holder != r_holder) - { - if (m_holder->decrement()) - delete m_holder; - m_holder = r_holder; - m_holder->increment(); - } - } - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // internal holder data structure + //////////////////////////////////////////////////////////////////////////////// + + template + class smart_ptr_holder + { + private: + unsigned m_count; + T* m_data; + + // make these private to disallow copying because the holder doesn't know how to copy + smart_ptr_holder(const smart_ptr_holder& s) : + m_count(0), m_data(0) + { + } + + smart_ptr_holder& operator=(const smart_ptr_holder& s) + { + return *this; + } + + public: + smart_ptr_holder(T* p = 0) : + m_count(1), m_data(p) + { + } + + ~smart_ptr_holder(void) + { + clear(); + } + + unsigned count(void) const + { + return m_count; + } + + void increment(void) + { + ++m_count; + } + + bool decrement(void) + { + --m_count; + return m_count == 0; + } + + bool null(void) + { + return m_data == 0; + } + + void clear(void) + { + if(m_data) + delete m_data; + m_data = 0; + } + + void set(T* p = 0) + { + clear(); + m_data = p; + } + + T*& pointer(void) + { + return m_data; + } + + const T* pointer(void) const + { + return m_data; + } + + T& value(void) + { + return *m_data; + } + + const T& value(void) const + { + return *m_data; + } + }; + + //////////////////////////////////////////////////////////////////////////////// + // smart_ptr_base class + //////////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////////// + // constructors, assignments and destructors + + // create a null pointer + template + smart_ptr_base::smart_ptr_base(void) : + m_holder(new smart_ptr_holder) + { + } + + // create a pointer containing a *copy* of the object pointer + template + smart_ptr_base::smart_ptr_base(const T& data) throw(illegal_copy) : + m_holder(new smart_ptr_holder) + { + m_holder->set(C()(data)); + } + + // create a pointer containing a dynamically created object + // Note: the object must be allocated *by the user* with new + // constructor form - must be called in the form smart_ptr x(new type(args)) + template + smart_ptr_base::smart_ptr_base(T* data) : + m_holder(new smart_ptr_holder) + { + m_holder->set(data); + } + + // copy constructor implements counted referencing - no copy is made + template + smart_ptr_base::smart_ptr_base(const smart_ptr_base& r) : + m_holder(0) + { + m_holder = r.m_holder; + m_holder->increment(); + } + + // assignment operator - required, else the output of GCC suffers segmentation faults + template + smart_ptr_base& smart_ptr_base::operator=(const smart_ptr_base& r) + { + alias(r); + return *this; + } + + // destructor decrements the reference count and delete only when the last reference is destroyed + template + smart_ptr_base::~smart_ptr_base(void) + { + if(m_holder->decrement()) + delete m_holder; + } + + ////////////////////////////////////////////////////////////////////////////// + // logical tests to see if there is anything contained in the pointer since it can be null + + template + bool smart_ptr_base::null(void) const + { + return m_holder->null(); + } + + template + bool smart_ptr_base::present(void) const + { + return !m_holder->null(); + } + + template + bool smart_ptr_base::operator!(void) const + { + return m_holder->null(); + } + + template + smart_ptr_base::operator bool(void) const + { + return !m_holder->null(); + } + + ////////////////////////////////////////////////////////////////////////////// + // dereference operators and functions + + template + T& smart_ptr_base::operator*(void) throw(null_dereference) + { + if (m_holder->null()) throw null_dereference("null pointer dereferenced in smart_ptr::operator*"); + return m_holder->value(); + } + + template + const T& smart_ptr_base::operator*(void) const throw(null_dereference) + { + if (m_holder->null()) throw null_dereference("null pointer dereferenced in smart_ptr::operator*"); + return m_holder->value(); + } + + template + T* smart_ptr_base::operator->(void) throw(null_dereference) + { + if (m_holder->null()) throw null_dereference("null pointer dereferenced in smart_ptr::operator->"); + return m_holder->pointer(); + } + + template + const T* smart_ptr_base::operator->(void) const throw(null_dereference) + { + if (m_holder->null()) throw null_dereference("null pointer dereferenced in smart_ptr::operator->"); + return m_holder->pointer(); + } + + ////////////////////////////////////////////////////////////////////////////// + // explicit function forms of the above assignment dereference operators + + template + void smart_ptr_base::set_value(const T& data) throw(illegal_copy) + { + m_holder->set(C()(data)); + } + + template + T& smart_ptr_base::value(void) throw(null_dereference) + { + if (m_holder->null()) throw null_dereference("null pointer dereferenced in smart_ptr::value"); + return m_holder->value(); + } + + template + const T& smart_ptr_base::value(void) const throw(null_dereference) + { + if (m_holder->null()) throw null_dereference("null pointer dereferenced in smart_ptr::value"); + return m_holder->value(); + } + + template + void smart_ptr_base::set(T* data) + { + m_holder->set(data); + } + + template + T* smart_ptr_base::pointer(void) + { + return m_holder->pointer(); + } + + template + const T* smart_ptr_base::pointer(void) const + { + return m_holder->pointer(); + } + + //////////////////////////////////////////////////////////////////////////////// + // functions to manage counted referencing + + // make this an alias of the passed object + template + void smart_ptr_base::alias(const smart_ptr_base& r) + { + _make_alias(r.m_holder); + } + + template + bool smart_ptr_base::aliases(const smart_ptr_base& r) const + { + return m_holder == r.m_holder; + } + + template + unsigned smart_ptr_base::alias_count(void) const + { + return m_holder->count(); + } + + template + void smart_ptr_base::clear(void) + { + m_holder->clear(); + } + + template + void smart_ptr_base::clear_unique(void) + { + if (m_holder->count() == 1) + m_holder->clear(); + else + { + m_holder->decrement(); + m_holder = 0; + m_holder = new smart_ptr_holder; + } + } + + template + void smart_ptr_base::make_unique(void) throw(illegal_copy) + { + if (m_holder->count() > 1) + { + smart_ptr_holder* old_holder = m_holder; + m_holder->decrement(); + m_holder = 0; + m_holder = new smart_ptr_holder; + if (old_holder->pointer()) + m_holder->set(C()(old_holder->value())); + } + } + + template + void smart_ptr_base::copy(const smart_ptr_base& data) throw(illegal_copy) + { + alias(data); + make_unique(); + } + + // internal function for distinguishing unique smart_ptr objects + // used for example in persistence routines + + template + smart_ptr_holder* smart_ptr_base::_handle(void) const + { + return m_holder; + } + + template + void smart_ptr_base::_make_alias(smart_ptr_holder* r_holder) + { + // make it alias-copy safe - this means that I don't try to do the + // assignment if r is either the same object or an alias of it + if (m_holder != r_holder) + { + if (m_holder->decrement()) + delete m_holder; + m_holder = r_holder; + m_holder->increment(); + } + } + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + diff --git a/src/stlplus/containers/triple.hpp b/src/stlplus/containers/triple.hpp index bf501f5..2b19087 100644 --- a/src/stlplus/containers/triple.hpp +++ b/src/stlplus/containers/triple.hpp @@ -1,54 +1,56 @@ -#ifndef STLPLUS_TRIPLE -#define STLPLUS_TRIPLE -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton, from an original by Dan Milton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Similar to the STL pair but with three elements - -//////////////////////////////////////////////////////////////////////////////// -#include "containers_fixes.hpp" - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // the triple class - - template - struct triple - { - typedef T1 first_type; - typedef T2 second_type; - typedef T3 third_type; - - T1 first; - T2 second; - T3 third; - - triple(void); - triple(const T1& p1, const T2& p2, const T3& p3); - triple(const triple& t2); - }; - - //////////////////////////////////////////////////////////////////////////////// - // creation - - template - triple make_triple(const T1& first, const T2& second, const T3& third); - - //////////////////////////////////////////////////////////////////////////////// - // comparison - - template - bool operator == (const triple& left, const triple& right); - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#include "triple.tpp" -#endif +#ifndef STLPLUS_TRIPLE +#define STLPLUS_TRIPLE +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton, from an original by Dan Milton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Similar to the STL pair but with three elements + +//////////////////////////////////////////////////////////////////////////////// +#include "containers_fixes.hpp" + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // the triple class + + template + struct triple + { + typedef T1 first_type; + typedef T2 second_type; + typedef T3 third_type; + + T1 first; + T2 second; + T3 third; + + triple(void); + triple(const T1& p1, const T2& p2, const T3& p3); + triple(const triple& t2); + }; + + //////////////////////////////////////////////////////////////////////////////// + // creation + + template + triple make_triple(const T1& first, const T2& second, const T3& third); + + //////////////////////////////////////////////////////////////////////////////// + // comparison + + template + bool operator == (const triple& left, const triple& right); + template + bool operator < (const triple& left, const triple& right); + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#include "triple.tpp" +#endif diff --git a/src/stlplus/containers/triple.tpp b/src/stlplus/containers/triple.tpp index 3c1fa30..50c2d70 100644 --- a/src/stlplus/containers/triple.tpp +++ b/src/stlplus/containers/triple.tpp @@ -1,56 +1,67 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton, from an original by Dan Milton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // the triple class - - template - triple::triple(void) : - first(), second(), third() - { - } - - template - triple::triple(const T1& p1, const T2& p2, const T3& p3) : - first(p1), second(p2), third(p3) - { - } - - template - triple::triple(const triple& t2) : - first(t2.first), second(t2.second), third(t2.third) - { - } - - //////////////////////////////////////////////////////////////////////////////// - // creation - - template - triple make_triple(const T1& first, const T2& second, const T3& third) - { - return triple(first,second,third); - } - - //////////////////////////////////////////////////////////////////////////////// - // comparison - - template - bool operator == (const triple& left, const triple& right) - { - // triples are equal if all elements are equal - return left.first == right.first && left.second == right.second && left.third == right.third; - } - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton, from an original by Dan Milton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // the triple class + + template + triple::triple(void) : + first(), second(), third() + { + } + + template + triple::triple(const T1& p1, const T2& p2, const T3& p3) : + first(p1), second(p2), third(p3) + { + } + + template + triple::triple(const triple& t2) : + first(t2.first), second(t2.second), third(t2.third) + { + } + + //////////////////////////////////////////////////////////////////////////////// + // creation + + template + triple make_triple(const T1& first, const T2& second, const T3& third) + { + return triple(first,second,third); + } + + //////////////////////////////////////////////////////////////////////////////// + // comparison + + template + bool operator == (const triple& left, const triple& right) + { + // triples are equal if all elements are equal + return left.first == right.first && left.second == right.second && left.third == right.third; + } + + template + bool operator < (const triple& left, const triple& right) + { + // use the < operator on each element + return left.first < right.first ? true : + right.first < left.first ? false : + left.second < right.second ? true : + right.second < left.second ? false : + left.third < right.third; + } + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + diff --git a/src/stlplus/portability/build.cpp b/src/stlplus/portability/build.cpp index e591a34..83679d4 100644 --- a/src/stlplus/portability/build.cpp +++ b/src/stlplus/portability/build.cpp @@ -1,60 +1,88 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// -#include "build.hpp" -#include "version.hpp" -#include "dprintf.hpp" -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // report the platform-specific details of this build - - std::string build(void) - { - //////////////////////////////////////////////////////////////////////////////// - // work out the platform - -#ifdef _WIN32 - std::string platform("Windows"); -#else - // at present there are no variations between different Unix platforms so - // they all map onto the generic Unix platform - std::string platform("Generic Unix"); -#endif - - //////////////////////////////////////////////////////////////////////////////// - // work out the compiler - -#if defined __GNUC__ - std::string compiler(dformat("gcc v%s",__VERSION__)); -#elif defined _MSC_VER - std::string compiler(dformat("MSVC v%0.2f",((float)_MSC_VER)/100.0)); -#elif defined __BORLANDC__ - std::string compiler(dformat("Borland v%d.%d",__BORLANDC__/256,__BORLANDC__/16%16)); -#else - std::string compiler("unknown compiler"); -#endif - - //////////////////////////////////////////////////////////////////////////////// - // work out the kind of build - // there are two variants - debug and release - -#ifndef NDEBUG - std::string variant("debug"); -#else - std::string variant("release"); -#endif - - return std::string("STLplus v") + version() + ", " + platform + ", " + compiler + ", " + variant; - } - -//////////////////////////////////////////////////////////////////////////////// -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// report the platform-specific details of this build + +//////////////////////////////////////////////////////////////////////////////// +#include "build.hpp" +#include "version.hpp" +#include "dprintf.hpp" +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + // STLplus version in the form "STLplus v3.0" - see version.hpp for a way of getting just the version number + std::string stlplus_version(void) + { + return std::string("STLplus v") + version(); + } + + // platform is the target operating system in the form "Windows" or "Generic Unix" + std::string platform(void) + { +#if defined _WIN32 + return std::string("Windows"); +#else + // at present there are no variations between different Unix platforms so + // they all map onto the generic Unix platform + return std::string("Generic Unix"); +#endif + } + + // compiler_name is the short name of the compiler, e.g. "gcc" or "MSVC" + std::string compiler_name(void) + { +#if defined __GNUC__ + return std::string("gcc"); +#elif defined _MSC_VER + return std::string("MSVC"); +#elif defined __BORLANDC__ + return std::string("Borland"); +#else + return std::string("unknown compiler"); +#endif + } + + // compiler_version is the version string of the compiler e.g. "3.4" for gcc or "15.00" for MSVC + std::string compiler_version(void) + { +#if defined __GNUC__ + return dformat("%d.%d.%d",__GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__); +#elif defined _MSC_VER + return dformat("%0.2f",((float)_MSC_VER)/100.0); +#elif defined __BORLANDC__ + return dformat("%d.%d%d",__BORLANDC__/256,__BORLANDC__/16%16,__BORLANDC__%16); +#else + return std::string(); +#endif + } + + // compiler is the compilation system and version above combined into a human- readable form e.g. "gcc v3.4" + std::string compiler(void) + { + return compiler_name() + std::string(" v") + compiler_version(); + } + + // variant is the kind of build - "debug" or "release" + std::string variant(void) + { +#ifndef NDEBUG + return std::string("debug"); +#else + return std::string("release"); +#endif + + } + + std::string build(void) + { + return stlplus_version() + ", " + platform() + ", " + compiler() + ", " + variant(); + } + +//////////////////////////////////////////////////////////////////////////////// +} // end namespace stlplus diff --git a/src/stlplus/portability/build.hpp b/src/stlplus/portability/build.hpp index a648beb..071bc65 100644 --- a/src/stlplus/portability/build.hpp +++ b/src/stlplus/portability/build.hpp @@ -1,34 +1,51 @@ -#ifndef STLPLUS_BUILD -#define STLPLUS_BUILD -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Provides a printable representation of the build characteristics in the form: - -// version, platform, compiler, variant - -// Where -// version is the version of STLplus -// platform is the target operating system -// compiler is the compilation system and version that the function was compiled with -// variant is the kind of build - debug or release - -// Example: -// STLplus version 3.0, Generic Unix, gcc v3.4, debug - -//////////////////////////////////////////////////////////////////////////////// -#include "portability_fixes.hpp" -#include - -namespace stlplus -{ - - std::string build(void); - -} -//////////////////////////////////////////////////////////////////////////////// -#endif +#ifndef STLPLUS_BUILD +#define STLPLUS_BUILD +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Provides a printable representation of the build characteristics in the form: + +// version, platform, compiler, variant + +// Where +// version is the version of STLplus +// platform is the target operating system +// compiler is the compilation system and version that the function was compiled with +// variant is the kind of build - debug or release + +// Example: +// STLplus version 3.0, Generic Unix, gcc v3.4, debug + +//////////////////////////////////////////////////////////////////////////////// +#include "portability_fixes.hpp" +#include + +namespace stlplus +{ + + // STLplus version in the form "STLplus version 3.0" - see version.hpp for a way of getting just the version number + std::string stlplus_version(void); + + // platform is the target operating system in the form "Windows" or "Generic Unix" + std::string platform(void); + + // compiler_name is the short name of the compiler, e.g. "gcc" or "MSVC" + std::string compiler_name(void); + // compiler_version is the version string of the compiler e.g. "3.4" for gcc or "15.00" for MSVC + std::string compiler_version(void); + // compiler is the compilation system and version above combined into a human- readable form e.g. "gcc v3.4" + std::string compiler(void); + + // variant is the kind of build - "debug" or "release" + std::string variant(void); + + // build is all of the above combined into a human-readable string + std::string build(void); + +} +//////////////////////////////////////////////////////////////////////////////// +#endif diff --git a/src/stlplus/portability/debug.cpp b/src/stlplus/portability/debug.cpp index d5b1534..a249844 100644 --- a/src/stlplus/portability/debug.cpp +++ b/src/stlplus/portability/debug.cpp @@ -1,187 +1,187 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// - -#include "debug.hpp" -#include "dprintf.hpp" -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - - static std::string format(const char* file, int line, const char* function, const char* message) - { - return dformat("%s:%d:%s: assertion failed: %s", - (file ? file : ""), - line, - (function ? function : "") , - (message ? message : "")); - } - - //////////////////////////////////////////////////////////////////////////////// - - assert_failed::assert_failed(const char* file, int line, const char* function, const char* message) - throw() : - std::logic_error(format(file, line, function, message)) - { - } - - assert_failed::~assert_failed(void) throw() - { - } - - //////////////////////////////////////////////////////////////////////////////// - - static unsigned _debug_depth = 0; - static bool _debug_global = false; - static bool _debug_set = false; - static bool _debug_recurse = false; - static bool _debug_read = false; - static char* _debug_match = 0; - static const debug_trace* debug_last = 0; - - void debug_global(const char* file, int line, const char* function, bool state) - { - _debug_global = state; - fprintf(stderr, "%s:%i:[%i]%s ", file, line, _debug_depth, function ? function : ""); - fprintf(stderr, "debug global : %s\n", _debug_global ? "on" : "off"); - } - - void debug_assert_fail(const char* file, int line, const char* function, const char* test) - throw(assert_failed) - { - fprintf(stderr, "%s:%i:[%i]%s: assertion failed: %s\n", - file, line, _debug_depth, function ? function : "", test); - if (debug_last) debug_last->stackdump(); - throw assert_failed(file, line, function, test); - } - - //////////////////////////////////////////////////////////////////////////////// - - debug_trace::debug_trace(const char* f, int l, const char* fn) : - m_file(f), m_line(l), m_function(fn ? fn : ""), - m_depth(0), m_last(debug_last), m_dbg(false), m_old(false) - { - if (!_debug_read) - { - _debug_match = getenv("DEBUG"); - _debug_recurse = getenv("DEBUG_LOCAL") == 0; - _debug_read = true; - } - m_dbg = _debug_set || (_debug_match && (!_debug_match[0] || (strcmp(_debug_match, m_file) == 0))); - m_old = _debug_set; - if (m_dbg && _debug_recurse) - _debug_set = true; - m_depth = ++_debug_depth; - debug_last = this; - if (debug()) report(std::string("entering ") + (m_function ? m_function : "")); - } - - debug_trace::~debug_trace(void) - { - if (debug()) report("leaving"); - --_debug_depth; - _debug_set = m_old; - debug_last = m_last; - } - - const char* debug_trace::file(void) const - { - return m_file; - } - - int debug_trace::line(void) const - { - return m_line; - } - - bool debug_trace::debug(void) const - { - return m_dbg || _debug_global; - } - - void debug_trace::debug_on(int l, bool recurse) - { - m_dbg = true; - m_old = _debug_set; - if (recurse) - _debug_set = true; - report(l, std::string("debug on") + (recurse ? " recursive" : "")); - } - - void debug_trace::debug_off(int l) - { - if (debug()) report(l, std::string("debug off")); - m_dbg = false; - _debug_set = m_old; - } - - void debug_trace::prefix(int l) const - { - fprintf(stderr, "%s:%i:[%i]%s ", m_file, l, m_depth, m_function ? m_function : ""); - } - - void debug_trace::do_report(int l, const std::string& message) const - { - prefix(l); - fprintf(stderr, "%s\n", message.c_str()); - fflush(stderr); - } - - void debug_trace::do_report(const std::string& message) const - { - do_report(m_line, message); - } - - void debug_trace::report(int l, const std::string& message) const - { - do_report(l, message); - } - - void debug_trace::report(const std::string& message) const - { - report(m_line, message); - } - - void debug_trace::error(int l, const std::string& message) const - { - do_report(l, "ERROR: " + message); - } - - void debug_trace::error(const std::string& message) const - { - error(m_line, message); - } - - void debug_trace::stackdump(int l, const std::string& message) const - { - do_report(l, message); - stackdump(); - } - - void debug_trace::stackdump(const std::string& message) const - { - stackdump(m_line, message); - } - - void debug_trace::stackdump(void) const - { - for (const debug_trace* item = this; item; item = item->m_last) - item->do_report("...called from here"); - } - - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// + +#include "debug.hpp" +#include "dprintf.hpp" +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + + static std::string format(const char* file, int line, const char* function, const char* message) + { + return dformat("%s:%d:%s: assertion failed: %s", + (file ? file : ""), + line, + (function ? function : "") , + (message ? message : "")); + } + + //////////////////////////////////////////////////////////////////////////////// + + assert_failed::assert_failed(const char* file, int line, const char* function, const char* message) + throw() : + std::logic_error(format(file, line, function, message)) + { + } + + assert_failed::~assert_failed(void) throw() + { + } + + //////////////////////////////////////////////////////////////////////////////// + + static unsigned _debug_depth = 0; + static bool _debug_global = false; + static bool _debug_set = false; + static bool _debug_recurse = false; + static bool _debug_read = false; + static char* _debug_match = 0; + static const debug_trace* debug_last = 0; + + void debug_global(const char* file, int line, const char* function, bool state) + { + _debug_global = state; + fprintf(stderr, "%s:%i:[%i]%s ", file, line, _debug_depth, function ? function : ""); + fprintf(stderr, "debug global : %s\n", _debug_global ? "on" : "off"); + } + + void debug_assert_fail(const char* file, int line, const char* function, const char* test) + throw(assert_failed) + { + fprintf(stderr, "%s:%i:[%i]%s: assertion failed: %s\n", + file, line, _debug_depth, function ? function : "", test); + if (debug_last) debug_last->stackdump(); + throw assert_failed(file, line, function, test); + } + + //////////////////////////////////////////////////////////////////////////////// + + debug_trace::debug_trace(const char* f, int l, const char* fn) : + m_file(f), m_line(l), m_function(fn ? fn : ""), + m_depth(0), m_last(debug_last), m_dbg(false), m_old(false) + { + if (!_debug_read) + { + _debug_match = getenv("DEBUG"); + _debug_recurse = getenv("DEBUG_LOCAL") == 0; + _debug_read = true; + } + m_dbg = _debug_set || (_debug_match && (!_debug_match[0] || (strcmp(_debug_match, m_file) == 0))); + m_old = _debug_set; + if (m_dbg && _debug_recurse) + _debug_set = true; + m_depth = ++_debug_depth; + debug_last = this; + if (debug()) report(std::string("entering ") + (m_function ? m_function : "")); + } + + debug_trace::~debug_trace(void) + { + if (debug()) report("leaving"); + --_debug_depth; + _debug_set = m_old; + debug_last = m_last; + } + + const char* debug_trace::file(void) const + { + return m_file; + } + + int debug_trace::line(void) const + { + return m_line; + } + + bool debug_trace::debug(void) const + { + return m_dbg || _debug_global; + } + + void debug_trace::debug_on(int l, bool recurse) + { + m_dbg = true; + m_old = _debug_set; + if (recurse) + _debug_set = true; + report(l, std::string("debug on") + (recurse ? " recursive" : "")); + } + + void debug_trace::debug_off(int l) + { + if (debug()) report(l, std::string("debug off")); + m_dbg = false; + _debug_set = m_old; + } + + void debug_trace::prefix(int l) const + { + fprintf(stderr, "%s:%i:[%i]%s ", m_file, l, m_depth, m_function ? m_function : ""); + } + + void debug_trace::do_report(int l, const std::string& message) const + { + prefix(l); + fprintf(stderr, "%s\n", message.c_str()); + fflush(stderr); + } + + void debug_trace::do_report(const std::string& message) const + { + do_report(m_line, message); + } + + void debug_trace::report(int l, const std::string& message) const + { + do_report(l, message); + } + + void debug_trace::report(const std::string& message) const + { + report(m_line, message); + } + + void debug_trace::error(int l, const std::string& message) const + { + do_report(l, "ERROR: " + message); + } + + void debug_trace::error(const std::string& message) const + { + error(m_line, message); + } + + void debug_trace::stackdump(int l, const std::string& message) const + { + do_report(l, message); + stackdump(); + } + + void debug_trace::stackdump(const std::string& message) const + { + stackdump(m_line, message); + } + + void debug_trace::stackdump(void) const + { + for (const debug_trace* item = this; item; item = item->m_last) + item->do_report("...called from here"); + } + + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus diff --git a/src/stlplus/portability/debug.hpp b/src/stlplus/portability/debug.hpp index 981490b..5b82538 100644 --- a/src/stlplus/portability/debug.hpp +++ b/src/stlplus/portability/debug.hpp @@ -1,128 +1,129 @@ -#ifndef STLPLUS_DEBUG -#define STLPLUS_DEBUG -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Set of simple debug utilities, all of which are switched off by the -// NDEBUG compiler directive - -//////////////////////////////////////////////////////////////////////////////// - -#include "portability_fixes.hpp" -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -// Problem with missing __FUNCTION__ macro -//////////////////////////////////////////////////////////////////////////////// -// this macro is used in debugging but was missing in Visual Studio prior to version 7 -// it also has a different name in Borland - -#if defined(_MSC_VER) && (_MSC_VER < 1300) -#define __FUNCTION__ 0 -#endif - -#ifdef __BORLANDC__ -#define __FUNCTION__ __FUNC__ -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Exception thrown if an assertion fails - -namespace stlplus -{ - - class assert_failed : public std::logic_error - { - public: - assert_failed(const char* file, int line, const char* function, const char* message) throw(); - ~assert_failed(void) throw(); - }; - -} // end namespace stlplus - - //////////////////////////////////////////////////////////////////////////////// - // The macros used in debugging - -#ifndef NDEBUG - -#define DEBUG_TRACE stlplus::debug_trace stlplus_debug_trace(__FILE__,__LINE__,__FUNCTION__) -#define IF_DEBUG(stmts) {if (stlplus_debug_trace.debug()){stlplus_debug_trace.prefix(__LINE__);stmts;}} -#define DEBUG_REPORT(str) IF_DEBUG(stlplus_debug_trace.report(__LINE__,str)) -#define DEBUG_ERROR(str) stlplus_debug_trace.error(__LINE__,str) -#define DEBUG_STACKDUMP(str) stlplus_debug_trace.stackdump(__LINE__,str) -#define DEBUG_ON stlplus_debug_trace.debug_on(__LINE__,true) -#define DEBUG_ON_LOCAL stlplus_debug_trace.debug_on(__LINE__,false) -#define DEBUG_ON_GLOBAL stlplus::debug_global(__FILE__,__LINE__,__FUNCTION__,true) -#define DEBUG_OFF_GLOBAL stlplus::debug_global(__FILE__,__LINE__,__FUNCTION__,false) -#define DEBUG_OFF stlplus_debug_trace.debug_off(__LINE__) -#define DEBUG_ASSERT(test) if (!(test))stlplus::debug_assert_fail(__FILE__,__LINE__,__FUNCTION__,#test) - -#else - -#define DEBUG_TRACE -#define IF_DEBUG(stmts) -#define DEBUG_REPORT(str) -#define DEBUG_ERROR(str) -#define DEBUG_STACKDUMP(str) -#define DEBUG_ON -#define DEBUG_ON_LOCAL -#define DEBUG_ON_GLOBAL -#define DEBUG_OFF_GLOBAL -#define DEBUG_OFF -#define DEBUG_ASSERT(test) - -#endif - -//////////////////////////////////////////////////////////////////////////////// -// infrastructure - don't use directly - -namespace stlplus -{ - - void debug_global(const char* file, int line, const char* function, bool state = true); - void debug_assert_fail(const char* file, int line, const char* function, const char* test) throw(assert_failed); - - class debug_trace - { - public: - debug_trace(const char* f, int l, const char* fn); - ~debug_trace(void); - const char* file(void) const; - int line(void) const; - bool debug(void) const; - void debug_on(int l, bool recurse); - void debug_off(int l); - void prefix(int l) const; - void report(int l, const std::string& message) const; - void report(const std::string& message) const; - void error(int l, const std::string& message) const; - void error(const std::string& message) const; - void stackdump(int l, const std::string& message) const; - void stackdump(const std::string& message) const; - void stackdump(void) const; - - private: - const char* m_file; - int m_line; - const char* m_function; - unsigned m_depth; - const debug_trace* m_last; - bool m_dbg; - bool m_old; - void do_report(int l, const std::string& message) const; - void do_report(const std::string& message) const; - - // make this class uncopyable - debug_trace(const debug_trace&); - debug_trace& operator = (const debug_trace&); - }; - -} // end namespace stlplus - - //////////////////////////////////////////////////////////////////////////////// -#endif +#ifndef STLPLUS_DEBUG +#define STLPLUS_DEBUG +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Set of simple debug utilities, all of which are switched off by the +// NDEBUG compiler directive + +//////////////////////////////////////////////////////////////////////////////// + +#include "portability_fixes.hpp" +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +// Problem with missing __FUNCTION__ macro +//////////////////////////////////////////////////////////////////////////////// + +// this macro is used in debugging but was missing in Visual Studio prior to version 7 +#if defined(_MSC_VER) && (_MSC_VER < 1300) +#define __FUNCTION__ 0 +#endif + +// old versions of Borland compiler defined a macro __FUNC__ but more recent ones define __FUNCTION__ +// I'm not sure at what version this change was made - assumed C++ Builder 6.32 +#if defined(__BORLANDC__) && (__BORLANDC__ < 1585) +#define __FUNCTION__ __FUNC__ +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Exception thrown if an assertion fails + +namespace stlplus +{ + + class assert_failed : public std::logic_error + { + public: + assert_failed(const char* file, int line, const char* function, const char* message) throw(); + ~assert_failed(void) throw(); + }; + +} // end namespace stlplus + + //////////////////////////////////////////////////////////////////////////////// + // The macros used in debugging + +#ifndef NDEBUG + +#define DEBUG_TRACE stlplus::debug_trace stlplus_debug_trace(__FILE__,__LINE__,__FUNCTION__) +#define IF_DEBUG(stmts) {if (stlplus_debug_trace.debug()){stlplus_debug_trace.prefix(__LINE__);stmts;}} +#define DEBUG_REPORT(str) IF_DEBUG(stlplus_debug_trace.report(__LINE__,str)) +#define DEBUG_ERROR(str) stlplus_debug_trace.error(__LINE__,str) +#define DEBUG_STACKDUMP(str) stlplus_debug_trace.stackdump(__LINE__,str) +#define DEBUG_ON stlplus_debug_trace.debug_on(__LINE__,true) +#define DEBUG_ON_LOCAL stlplus_debug_trace.debug_on(__LINE__,false) +#define DEBUG_ON_GLOBAL stlplus::debug_global(__FILE__,__LINE__,__FUNCTION__,true) +#define DEBUG_OFF_GLOBAL stlplus::debug_global(__FILE__,__LINE__,__FUNCTION__,false) +#define DEBUG_OFF stlplus_debug_trace.debug_off(__LINE__) +#define DEBUG_ASSERT(test) if (!(test))stlplus::debug_assert_fail(__FILE__,__LINE__,__FUNCTION__,#test) + +#else + +#define DEBUG_TRACE +#define IF_DEBUG(stmts) +#define DEBUG_REPORT(str) +#define DEBUG_ERROR(str) +#define DEBUG_STACKDUMP(str) +#define DEBUG_ON +#define DEBUG_ON_LOCAL +#define DEBUG_ON_GLOBAL +#define DEBUG_OFF_GLOBAL +#define DEBUG_OFF +#define DEBUG_ASSERT(test) + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// infrastructure - don't use directly + +namespace stlplus +{ + + void debug_global(const char* file, int line, const char* function, bool state = true); + void debug_assert_fail(const char* file, int line, const char* function, const char* test) throw(assert_failed); + + class debug_trace + { + public: + debug_trace(const char* f, int l, const char* fn); + ~debug_trace(void); + const char* file(void) const; + int line(void) const; + bool debug(void) const; + void debug_on(int l, bool recurse); + void debug_off(int l); + void prefix(int l) const; + void report(int l, const std::string& message) const; + void report(const std::string& message) const; + void error(int l, const std::string& message) const; + void error(const std::string& message) const; + void stackdump(int l, const std::string& message) const; + void stackdump(const std::string& message) const; + void stackdump(void) const; + + private: + const char* m_file; + int m_line; + const char* m_function; + unsigned m_depth; + const debug_trace* m_last; + bool m_dbg; + bool m_old; + void do_report(int l, const std::string& message) const; + void do_report(const std::string& message) const; + + // make this class uncopyable + debug_trace(const debug_trace&); + debug_trace& operator = (const debug_trace&); + }; + +} // end namespace stlplus + + //////////////////////////////////////////////////////////////////////////////// +#endif diff --git a/src/stlplus/portability/dprintf.cpp b/src/stlplus/portability/dprintf.cpp index b0e05c6..d9a9e4f 100644 --- a/src/stlplus/portability/dprintf.cpp +++ b/src/stlplus/portability/dprintf.cpp @@ -1,92 +1,92 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Notes: - -// Feb 2007: Rewritten in terms of platform-specific fixes to the -// buffer-overflow problem. Using native functions for this has the added -// benefit of giving access to other features of the C-runtime such as Unicode -// support. - -//////////////////////////////////////////////////////////////////////////////// - -#include "dprintf.hpp" -#include -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - -//////////////////////////////////////////////////////////////////////////////// - - int vdprintf(std::string& formatted, const char* format, va_list args) - { -#ifdef MSWINDOWS - int length = 0; - char* buffer = 0; - for(int buffer_length = 256; ; buffer_length*=2) - { - buffer = (char*)malloc(buffer_length); - if (!buffer) return -1; - length = _vsnprintf(buffer, buffer_length-1, format, args); - if (length >= 0) - { - buffer[length] = 0; - formatted += std::string(buffer); - free(buffer); - break; - } - free(buffer); - } - return length; -#else - char* buffer = 0; - int length = vasprintf(&buffer, format, args); - if (!buffer) return -1; - if (length >= 0) - formatted += std::string(buffer); - free(buffer); - return length; -#endif - } - - int dprintf(std::string& formatted, const char* format, ...) - { - va_list args; - va_start(args, format); - int result = vdprintf(formatted, format, args); - va_end(args); - return result; - } - - std::string vdformat(const char* format, va_list args) throw(std::invalid_argument) - { - std::string formatted; - int length = vdprintf(formatted, format, args); - if (length < 0) throw std::invalid_argument("dprintf"); - return formatted; - } - - std::string dformat(const char* format, ...) throw(std::invalid_argument) - { - std::string formatted; - va_list args; - va_start(args, format); - int length = vdprintf(formatted, format, args); - va_end(args); - if (length < 0) throw std::invalid_argument("dprintf"); - return formatted; - } - -//////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Notes: + +// Feb 2007: Rewritten in terms of platform-specific fixes to the +// buffer-overflow problem. Using native functions for this has the added +// benefit of giving access to other features of the C-runtime such as Unicode +// support. + +//////////////////////////////////////////////////////////////////////////////// + +#include "dprintf.hpp" +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + +//////////////////////////////////////////////////////////////////////////////// + + int vdprintf(std::string& formatted, const char* format, va_list args) + { +#ifdef MSWINDOWS + int length = 0; + char* buffer = 0; + for(int buffer_length = 256; ; buffer_length*=2) + { + buffer = (char*)malloc(buffer_length); + if (!buffer) return -1; + length = _vsnprintf(buffer, buffer_length-1, format, args); + if (length >= 0) + { + buffer[length] = 0; + formatted += std::string(buffer); + free(buffer); + break; + } + free(buffer); + } + return length; +#else + char* buffer = 0; + int length = vasprintf(&buffer, format, args); + if (!buffer) return -1; + if (length >= 0) + formatted += std::string(buffer); + free(buffer); + return length; +#endif + } + + int dprintf(std::string& formatted, const char* format, ...) + { + va_list args; + va_start(args, format); + int result = vdprintf(formatted, format, args); + va_end(args); + return result; + } + + std::string vdformat(const char* format, va_list args) throw(std::invalid_argument) + { + std::string formatted; + int length = vdprintf(formatted, format, args); + if (length < 0) throw std::invalid_argument("dprintf"); + return formatted; + } + + std::string dformat(const char* format, ...) throw(std::invalid_argument) + { + std::string formatted; + va_list args; + va_start(args, format); + int length = vdprintf(formatted, format, args); + va_end(args); + if (length < 0) throw std::invalid_argument("dprintf"); + return formatted; + } + +//////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus diff --git a/src/stlplus/portability/dprintf.hpp b/src/stlplus/portability/dprintf.hpp index 360e5c1..3e4aa92 100644 --- a/src/stlplus/portability/dprintf.hpp +++ b/src/stlplus/portability/dprintf.hpp @@ -1,122 +1,122 @@ -#ifndef STLPLUS_DPRINTF -#define STLPLUS_DPRINTF -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Provides an sprintf-like function acting on STL strings. The 'd' in dprintf -// stands for "dynamic" in that the string is a dynamic string whereas a char* -// buffer would be static (in size that is, not static in C terms). - -// The obvious solution to the problem of in-memory formatted output is to use -// sprintf(), but this is a potentially dangerous operation since it will quite -// happily charge off the end of the string it is printing to and thereby -// corrupt memory. This kind of buffer-overflow vulnerability is the source of -// most security failures exploited by virus-writers. It means that sprintf -// should *never* be used and should be made obsolete. - -// In any case, using arbitrary-sized fixed-length buffers is not part of any -// quality-orientated design philosophy. - -// Most operating systems now have a safe version of sprintf, but this is -// non-standard. The functions in this file are platform-independent interfaces -// to the underlying safe implementation. - -// I would like to make this set of functions obsolete too, since I believe the -// C runtime should be deprecated in favour of C++ runtime which uses dynamic -// strings and can handle exceptions. However, there is as yet no C++ -// equivalent functionality to some of the string-handling available through -// the printf-like functions, so it has to stay for now. - -// int dprintf (std::string& buffer, const char* format, ...); - -// Formats the message by appending to the std::string buffer according to -// the formatting codes in the format string. The return int is the number -// of characters generated by this call, i.e. the increase in the length of -// the std::string. - -// int vdprintf (std::string& buffer, const char* format, va_list args); - -// As above, but using a pre-initialised va_args argument list. Useful for -// nesting dprintf calls within variable argument functions. - -// std::string dformat (const char* format, ...); - -// Similar to dprintf() above, except the result is formatted into a new -// std::string which is returned by the function. Very useful for inline -// calls within an iostream expression. - -// e.g. cout << "Total: " << dformat("%6i",t) << endl; - -// std::string vdformat (const char* format, va_list); - -// As above, but using a pre-initialised va_args argument list. Useful for nesting -// dformat calls within variable argument functions. - -// The format string supports the following format codes as in the C runtime library: - -// % [ flags ] [ field ] [ . precision ] [ modifier ] [ conversion ] - -// flags: -// - - left justified -// + - print sign for +ve numbers -// ' ' - leading space where + sign would be -// 0 - leading zeros to width of field -// # - alternate format - -// field: -// a numeric argument specifying the field width - default = 0 -// * means take the next va_arg as the field width - if negative then left justify - -// precision: -// a numeric argument the meaning of which depends on the conversion - -// - %s - max characters from a string - default = strlen() -// - %e, %f - decimal places to be displayed - default = 6 -// - %g - significant digits to be displayed - default = 6 -// - all integer conversions - minimum digits to display - default = 0 -// * means take the next va_arg as the field width - if negative then left justify - -// modifier: -// h - short or unsigned short -// l - long or unsigned long -// L - long double - -// conversions: -// d, i - short/int/long as decimal -// u - short/int/long as unsigned decimal -// o - short/int/long as unsigned octal - # adds leading 0 -// x, X - short/int/long as unsigned hexadecimal - # adds leading 0x -// c - char -// s - char* -// f - double/long double as fixed point -// e, E - double/long double as floating point -// g, G - double/long double as fixed point/floating point depending on value -// p - void* as unsigned hexadecimal -// % - literal % -// n - int* as recipient of length of formatted string so far - -//////////////////////////////////////////////////////////////////////////////// -#include "portability_fixes.hpp" -#include -#include -#include - -namespace stlplus -{ - - // format by appending to a string and return the increase in length - // if there is an error, return a negative number and leave the string unchanged - int dprintf (std::string& formatted, const char* format, ...); - int vdprintf (std::string& formatted, const char* format, va_list args); - - // format into a new string and return the result - // if there is an error, throw an exception - std::string dformat (const char* format, ...) throw(std::invalid_argument); - std::string vdformat (const char* format, va_list) throw(std::invalid_argument); - -} // end namespace stlplus - -#endif +#ifndef STLPLUS_DPRINTF +#define STLPLUS_DPRINTF +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Provides an sprintf-like function acting on STL strings. The 'd' in dprintf +// stands for "dynamic" in that the string is a dynamic string whereas a char* +// buffer would be static (in size that is, not static in C terms). + +// The obvious solution to the problem of in-memory formatted output is to use +// sprintf(), but this is a potentially dangerous operation since it will quite +// happily charge off the end of the string it is printing to and thereby +// corrupt memory. This kind of buffer-overflow vulnerability is the source of +// most security failures exploited by virus-writers. It means that sprintf +// should *never* be used and should be made obsolete. + +// In any case, using arbitrary-sized fixed-length buffers is not part of any +// quality-orientated design philosophy. + +// Most operating systems now have a safe version of sprintf, but this is +// non-standard. The functions in this file are platform-independent interfaces +// to the underlying safe implementation. + +// I would like to make this set of functions obsolete too, since I believe the +// C runtime should be deprecated in favour of C++ runtime which uses dynamic +// strings and can handle exceptions. However, there is as yet no C++ +// equivalent functionality to some of the string-handling available through +// the printf-like functions, so it has to stay for now. + +// int dprintf (std::string& buffer, const char* format, ...); + +// Formats the message by appending to the std::string buffer according to +// the formatting codes in the format string. The return int is the number +// of characters generated by this call, i.e. the increase in the length of +// the std::string. + +// int vdprintf (std::string& buffer, const char* format, va_list args); + +// As above, but using a pre-initialised va_args argument list. Useful for +// nesting dprintf calls within variable argument functions. + +// std::string dformat (const char* format, ...); + +// Similar to dprintf() above, except the result is formatted into a new +// std::string which is returned by the function. Very useful for inline +// calls within an iostream expression. + +// e.g. cout << "Total: " << dformat("%6i",t) << endl; + +// std::string vdformat (const char* format, va_list); + +// As above, but using a pre-initialised va_args argument list. Useful for nesting +// dformat calls within variable argument functions. + +// The format string supports the following format codes as in the C runtime library: + +// % [ flags ] [ field ] [ . precision ] [ modifier ] [ conversion ] + +// flags: +// - - left justified +// + - print sign for +ve numbers +// ' ' - leading space where + sign would be +// 0 - leading zeros to width of field +// # - alternate format + +// field: +// a numeric argument specifying the field width - default = 0 +// * means take the next va_arg as the field width - if negative then left justify + +// precision: +// a numeric argument the meaning of which depends on the conversion - +// - %s - max characters from a string - default = strlen() +// - %e, %f - decimal places to be displayed - default = 6 +// - %g - significant digits to be displayed - default = 6 +// - all integer conversions - minimum digits to display - default = 0 +// * means take the next va_arg as the field width - if negative then left justify + +// modifier: +// h - short or unsigned short +// l - long or unsigned long +// L - long double + +// conversions: +// d, i - short/int/long as decimal +// u - short/int/long as unsigned decimal +// o - short/int/long as unsigned octal - # adds leading 0 +// x, X - short/int/long as unsigned hexadecimal - # adds leading 0x +// c - char +// s - char* +// f - double/long double as fixed point +// e, E - double/long double as floating point +// g, G - double/long double as fixed point/floating point depending on value +// p - void* as unsigned hexadecimal +// % - literal % +// n - int* as recipient of length of formatted string so far + +//////////////////////////////////////////////////////////////////////////////// +#include "portability_fixes.hpp" +#include +#include +#include + +namespace stlplus +{ + + // format by appending to a string and return the increase in length + // if there is an error, return a negative number and leave the string unchanged + int dprintf (std::string& formatted, const char* format, ...); + int vdprintf (std::string& formatted, const char* format, va_list args); + + // format into a new string and return the result + // if there is an error, throw an exception + std::string dformat (const char* format, ...) throw(std::invalid_argument); + std::string vdformat (const char* format, va_list) throw(std::invalid_argument); + +} // end namespace stlplus + +#endif diff --git a/src/stlplus/portability/dynaload.cpp b/src/stlplus/portability/dynaload.cpp index 4cd5741..a5d8592 100644 --- a/src/stlplus/portability/dynaload.cpp +++ b/src/stlplus/portability/dynaload.cpp @@ -1,184 +1,184 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// -#include "dynaload.hpp" - -#ifdef MSWINDOWS -#include -#else -#include -#endif - -//////////////////////////////////////////////////////////////////////////////// - -#ifdef MSWINDOWS - -static std::string last_error(void) -{ - // get the last error code - if none, return the empty string - DWORD err = GetLastError(); - if (err == 0) return std::string(); - // get the system message for this error code - char* message; - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, - 0, - err, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR)&message, - 0,0); - std::string result = message; - LocalFree(message); - // the error message is for some perverse reason newline terminated - remove this - if (result[result.size()-1] == '\n') - result.erase(result.end()-1); - if (result[result.size()-1] == '\r') - result.erase(result.end()-1); - return result; -} - -#else - -static std::string last_error(void) -{ - return std::string(dlerror()); -} - -#endif - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // library management - - // construct the object but do not load - dynaload::dynaload(void) : m_handle(0) - { - } - - // construct and load - dynaload::dynaload(const std::string& library) : m_handle(0) - { - load(library); - } - - // destroy and unload if loaded - dynaload::~dynaload(void) - { - unload(); - } - - // load the library - return success or fail - bool dynaload::load(const std::string& library) - { -#ifdef MSWINDOWS - m_handle = (void*)LoadLibrary(library.c_str()); -#elif defined(CYGWIN) - m_handle = dlopen(library.c_str(),RTLD_NOW); -#else - std::string full_library = std::string("lib") + library + std::string(".so"); - m_handle = dlopen(full_library.c_str(),RTLD_NOW); -#endif - if (!m_handle) - { - m_error = load_error; - m_text = last_error(); - } - return loaded(); - } - - // unload the library if loaded - bool dynaload::unload(void) - { - if (!loaded()) return false; -#ifdef MSWINDOWS - int status = FreeLibrary((HINSTANCE)m_handle) ? 0 : 1; -#else - int status = dlclose(m_handle); -#endif - if (status != 0) - { - m_error = unload_error; - m_text = last_error(); - } - return status == 0; - } - - // test whether the library is loaded - bool dynaload::loaded(void) const - { - return m_handle != 0; - } - - //////////////////////////////////////////////////////////////////////////// - // symbol management - - // test whether a function is exported by the library - // does not set the error flag if fails - bool dynaload::present(const std::string& name) - { - if (!loaded()) return false; -#ifdef MSWINDOWS - void* fn = (void*)GetProcAddress((HINSTANCE)m_handle,name.c_str()); -#else - void* fn = dlsym(m_handle,name.c_str()); -#endif - return fn != 0; - } - - // get the function as a generic pointer - void* dynaload::symbol(const std::string& name) - { - if (!loaded()) return 0; -#ifdef MSWINDOWS - void* fn = (void*)GetProcAddress((HINSTANCE)m_handle,name.c_str()); -#else - void* fn = dlsym(m_handle,name.c_str()); -#endif - if (!fn) - { - m_error = symbol_error; - m_text = last_error(); - } - return fn; - } - - //////////////////////////////////////////////////////////////////////////// - // error management - - // test whether there has been an error - bool dynaload::error(void) const - { - return m_error != no_error; - } - - // clear an error once it has been handled (or ignored) - void dynaload::clear_error(void) - { - m_error = no_error; - m_text = std::string(); - } - - // get the type of the error as indicated by the enum error_t - dynaload::error_t dynaload::error_type(void) const - { - return m_error; - } - - // get the text of the error as provided by the OS - std::string dynaload::error_text(void) const - { - return m_text; - } - - -//////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// +#include "dynaload.hpp" + +#ifdef MSWINDOWS +#include +#else +#include +#endif + +//////////////////////////////////////////////////////////////////////////////// + +#ifdef MSWINDOWS + +static std::string last_error(void) +{ + // get the last error code - if none, return the empty string + DWORD err = GetLastError(); + if (err == 0) return std::string(); + // get the system message for this error code + char* message; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, + 0, + err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&message, + 0,0); + std::string result = message; + LocalFree(message); + // the error message is for some perverse reason newline terminated - remove this + if (result[result.size()-1] == '\n') + result.erase(result.end()-1); + if (result[result.size()-1] == '\r') + result.erase(result.end()-1); + return result; +} + +#else + +static std::string last_error(void) +{ + return std::string(dlerror()); +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // library management + + // construct the object but do not load + dynaload::dynaload(void) : m_handle(0) + { + } + + // construct and load + dynaload::dynaload(const std::string& library) : m_handle(0) + { + load(library); + } + + // destroy and unload if loaded + dynaload::~dynaload(void) + { + unload(); + } + + // load the library - return success or fail + bool dynaload::load(const std::string& library) + { +#ifdef MSWINDOWS + m_handle = (void*)LoadLibraryA(library.c_str()); +#elif defined(CYGWIN) + m_handle = dlopen(library.c_str(),RTLD_NOW); +#else + std::string full_library = std::string("lib") + library + std::string(".so"); + m_handle = dlopen(full_library.c_str(),RTLD_NOW); +#endif + if (!m_handle) + { + m_error = load_error; + m_text = last_error(); + } + return loaded(); + } + + // unload the library if loaded + bool dynaload::unload(void) + { + if (!loaded()) return false; +#ifdef MSWINDOWS + int status = FreeLibrary((HINSTANCE)m_handle) ? 0 : 1; +#else + int status = dlclose(m_handle); +#endif + if (status != 0) + { + m_error = unload_error; + m_text = last_error(); + } + return status == 0; + } + + // test whether the library is loaded + bool dynaload::loaded(void) const + { + return m_handle != 0; + } + + //////////////////////////////////////////////////////////////////////////// + // symbol management + + // test whether a function is exported by the library + // does not set the error flag if fails + bool dynaload::present(const std::string& name) + { + if (!loaded()) return false; +#ifdef MSWINDOWS + void* fn = (void*)GetProcAddress((HINSTANCE)m_handle,name.c_str()); +#else + void* fn = dlsym(m_handle,name.c_str()); +#endif + return fn != 0; + } + + // get the function as a generic pointer + void* dynaload::symbol(const std::string& name) + { + if (!loaded()) return 0; +#ifdef MSWINDOWS + void* fn = (void*)GetProcAddress((HINSTANCE)m_handle,name.c_str()); +#else + void* fn = dlsym(m_handle,name.c_str()); +#endif + if (!fn) + { + m_error = symbol_error; + m_text = last_error(); + } + return fn; + } + + //////////////////////////////////////////////////////////////////////////// + // error management + + // test whether there has been an error + bool dynaload::error(void) const + { + return m_error != no_error; + } + + // clear an error once it has been handled (or ignored) + void dynaload::clear_error(void) + { + m_error = no_error; + m_text = std::string(); + } + + // get the type of the error as indicated by the enum error_t + dynaload::error_t dynaload::error_type(void) const + { + return m_error; + } + + // get the text of the error as provided by the OS + std::string dynaload::error_text(void) const + { + return m_text; + } + + +//////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus diff --git a/src/stlplus/portability/dynaload.hpp b/src/stlplus/portability/dynaload.hpp index d35d324..edfee98 100644 --- a/src/stlplus/portability/dynaload.hpp +++ b/src/stlplus/portability/dynaload.hpp @@ -1,86 +1,86 @@ -#ifndef STLPLUS_DYNALOAD -#define STLPLUS_DYNALOAD -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// A portable interface to the dynamic loader - i.e. the system for loading -// dynamic libraries or shared libraries during the running of a program, -// rather than by linking - -//////////////////////////////////////////////////////////////////////////////// -#include "portability_fixes.hpp" -#include - -namespace stlplus -{ - - ////////////////////////////////////////////////////////////////////////////// - // dynaload class manages a dynamic loadable library and unloads it on destruction - - class dynaload - { - public: - - //////////////////////////////////////////////////////////////////////////// - // library management - - // construct the object but do not load - dynaload(void); - - // construct and load - dynaload(const std::string& library); - - // destroy and unload if loaded - ~dynaload(void); - - // load the library - return success or fail - bool load(const std::string& library); - - // unload the library if loaded - bool unload(void); - - // test whether the library is loaded - bool loaded(void) const; - - //////////////////////////////////////////////////////////////////////////// - // symbol management - - // test whether a function is exported by the library - bool present(const std::string& name); - - // get the function as a generic pointer - void* symbol(const std::string& name); - - //////////////////////////////////////////////////////////////////////////// - // error management - - // enum values to indicate type of error - enum error_t {no_error, load_error, unload_error, symbol_error}; - - // test whether there has been an error - bool error(void) const; - - // clear an error once it has been handled (or ignored) - void clear_error(void); - - // get the type of the error as indicated by the enum error_t - error_t error_type(void) const; - - // get the text of the error as provided by the OS - std::string error_text(void) const; - - //////////////////////////////////////////////////////////////////////////// - - private: - void* m_handle; - error_t m_error; - std::string m_text; - }; - -} - -#endif +#ifndef STLPLUS_DYNALOAD +#define STLPLUS_DYNALOAD +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// A portable interface to the dynamic loader - i.e. the system for loading +// dynamic libraries or shared libraries during the running of a program, +// rather than by linking + +//////////////////////////////////////////////////////////////////////////////// +#include "portability_fixes.hpp" +#include + +namespace stlplus +{ + + ////////////////////////////////////////////////////////////////////////////// + // dynaload class manages a dynamic loadable library and unloads it on destruction + + class dynaload + { + public: + + //////////////////////////////////////////////////////////////////////////// + // library management + + // construct the object but do not load + dynaload(void); + + // construct and load + dynaload(const std::string& library); + + // destroy and unload if loaded + ~dynaload(void); + + // load the library - return success or fail + bool load(const std::string& library); + + // unload the library if loaded + bool unload(void); + + // test whether the library is loaded + bool loaded(void) const; + + //////////////////////////////////////////////////////////////////////////// + // symbol management + + // test whether a function is exported by the library + bool present(const std::string& name); + + // get the function as a generic pointer + void* symbol(const std::string& name); + + //////////////////////////////////////////////////////////////////////////// + // error management + + // enum values to indicate type of error + enum error_t {no_error, load_error, unload_error, symbol_error}; + + // test whether there has been an error + bool error(void) const; + + // clear an error once it has been handled (or ignored) + void clear_error(void); + + // get the type of the error as indicated by the enum error_t + error_t error_type(void) const; + + // get the text of the error as provided by the OS + std::string error_text(void) const; + + //////////////////////////////////////////////////////////////////////////// + + private: + void* m_handle; + error_t m_error; + std::string m_text; + }; + +} + +#endif diff --git a/src/stlplus/portability/file_system.cpp b/src/stlplus/portability/file_system.cpp index cfca256..cac3292 100644 --- a/src/stlplus/portability/file_system.cpp +++ b/src/stlplus/portability/file_system.cpp @@ -1,1058 +1,1121 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// This is a portable interface to the file system. - -// The idea is that you write all file system access code using these functions, -// which are ported to all platforms that we are interested in. Therefore your -// code is inherently portable. - -// Native Windows version: switched on by macro _WIN32 which is defined by VC++/Borland/Mingw compilers -// Unix/Gnu version: default variant, no compiler directives are required but _WIN32 must be absent -// Cygwin/Gnu version: as Unix version but with additional support for Windows drive letters - -//////////////////////////////////////////////////////////////////////////////// -#include "file_system.hpp" -#include "wildcard.hpp" -#include -#include -#include -#include -#include - -#ifdef MSWINDOWS -#include -#include -#include -#include -#include -#include -#include -#else -#include -#include -#include -#include -#include -#include -#endif - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - -//////////////////////////////////////////////////////////////////////////////// -// definitions of separators - -#ifdef MSWINDOWS - static const char* separator_set = "\\/"; - static const char preferred_separator = '\\'; -#else - static const char* separator_set = "/"; - static const char preferred_separator = '/'; -#endif - - static bool is_separator (char ch) - { - for (int i = 0; separator_set[i]; i++) - { - if (separator_set[i] == ch) - return true; - } - return false; - } - -//////////////////////////////////////////////////////////////////////////////// -// implement string comparison of paths - Unix is case-sensitive, Windoze is case-insensitive - -#ifdef MSWINDOWS - - static std::string lowercase(const std::string& val) - { - std::string text = val; - for (unsigned i = 0; i < text.size(); i++) - text[i] = tolower(text[i]); - return text; - } - -#endif - - bool path_compare(const std::string& l, const std::string& r) - { -#ifdef MSWINDOWS - return lowercase(l) == lowercase(r); -#else - return l == r; -#endif - } - -//////////////////////////////////////////////////////////////////////////////// -// Internal data structure used to hold the different parts of a filespec - - class file_specification - { - private: - bool m_relative; // true = relative, false = absolute - std::string m_drive; // drive - drive letter (e.g. "c:") or the path for an UNC (e.g. "\\somewhere") - // empty if not known or on Unix - std::vector m_path; // the subdirectory path to follow from the drive - std::string m_filename; // the filename - public: - file_specification(void) : m_relative(false) {} - ~file_specification(void) {} - - bool initialise_folder(const std::string& spec); - bool initialise_file(const std::string& spec); - bool simplify(void); - bool make_absolute(const std::string& root = folder_current_full()); - bool make_absolute(const file_specification& root); - bool make_relative(const std::string& root = folder_current_full()); - bool make_relative(const file_specification& root); - bool relative(void) const {return m_relative;} - bool absolute(void) const {return !relative();} - void set_relative(void) {m_relative = true;} - void set_absolute(void) {m_relative = false;} - - const std::string& drive(void) const {return m_drive;} - std::string& drive(void) {return m_drive;} - void set_drive(const std::string& drive) {m_drive = drive;} - - const std::vector& path(void) const {return m_path;} - std::vector& path(void) {return m_path;} - void set_path(const std::vector& path) {m_path = path;} - - void add_subpath(const std::string& subpath) {m_path.push_back(subpath);} - unsigned subpath_size(void) const {return m_path.size();} - const std::string& subpath_element(unsigned i) const {return m_path[i];} - void subpath_erase(unsigned i) {m_path.erase(m_path.begin()+i);} - - const std::string& file(void) const {return m_filename;} - std::string& file(void) {return m_filename;} - void set_file(const std::string& file) {m_filename = file;} - - std::string image(void) const; - }; - - bool file_specification::initialise_folder(const std::string& folder_spec) - { - std::string spec = folder_spec; - m_relative = true; - m_drive.erase(); - m_path.clear(); - m_filename.erase(); - unsigned i = 0; -#ifdef MSWINDOWS - // first split off the drive letter or UNC prefix on Windows - if (spec.size() >= 2 && isalpha(spec[0]) && spec[1] == ':') - { - // found a drive letter - i = 2; - m_drive = spec.substr(0, 2); - m_relative = false; - // if there is a drive but no path or a relative path, get the current - // path for this drive and prepend it to the path - if (i == spec.size() || !is_separator(spec[i])) - { - // getdcwd requires the drive number (1..26) not the letter (A..Z) - char path [MAX_PATH+1]; - int drivenum = toupper(m_drive[0]) - 'A' + 1; - if (_getdcwd(drivenum, path, MAX_PATH+1)) - { - // the path includes the drive so we have the drive info twice - // need to prepend this absolute path to the spec such that any remaining relative path is still retained - if (!is_separator(path[strlen(path)-1])) spec.insert(2, 1, preferred_separator); - spec.insert(2, path+2); - } - else - { - // non-existent drive - fill in just the root directory - spec.insert(2, 1, preferred_separator); - } - } - } - else if (spec.size() >= 2 && is_separator(spec[0]) && is_separator(spec[1])) - { - // found an UNC prefix - i = 2; - // find the end of the prefix by scanning for the next seperator or the end of the spec - while (i < spec.size() && !is_separator(spec[i])) i++; - m_drive = spec.substr(0, i); - m_relative = false; - } -#endif -#ifdef CYGWIN - // first split off the drive letter or UNC prefix on Windows - the Cygwin environment supports these too - if (spec.size() >= 2 && isalpha(spec[0]) && spec[1] == ':') - { - // found a drive letter - i = 2; - m_drive = spec.substr(0, 2); - m_relative = false; - // if there is a drive but no path or a relative path, get the current - // path for this drive and prepend it to the path - if (i == spec.size() || !is_separator(spec[i])) - { - // non-existent drive - fill in just the root directory - spec.insert(2, 1, preferred_separator); - } - } - else if (spec.size() >= 2 && is_separator(spec[0]) && is_separator(spec[1])) - { - // found an UNC prefix - i = 2; - // find the end of the prefix by scanning for the next seperator or the end of the spec - while (i < spec.size() && !is_separator(spec[i])) i++; - m_drive = spec.substr(0, i); - m_relative = false; - } -#endif - // check whether the path is absolute or relative and discard the leading / if absolute - if (i < spec.size() && is_separator(spec[i])) - { - m_relative = false; - i++; -#ifdef MSWINDOWS - // if there's no drive, fill it in on Windows since absolute paths must have a drive - if (m_drive.empty()) - { - m_drive += (char)(_getdrive() - 1 + 'A'); - m_drive += ':'; - } -#endif - } - // now extract the path elements - note that a trailing / is not significant since /a/b/c/ === /a/b/c - // also note that the leading / has been discarded - all paths are relative - // if absolute() is set, then paths are relative to the drive, else they are relative to the current path - unsigned start = i; - while(i <= spec.size()) - { - if (i == spec.size()) - { - // path element terminated by the end of the string - // discard this element if it is zero length because that represents the trailing / - if (i != start) - m_path.push_back(spec.substr(start, i-start)); - } - else if (is_separator(spec[i])) - { - // path element terminated by a separator - m_path.push_back(spec.substr(start, i-start)); - start = i+1; - } - i++; - } - // TODO - some error handling? - return true; - } - - bool file_specification::initialise_file(const std::string& spec) - { - m_filename.erase(); - // remove last element as the file and then treat the rest as a folder - unsigned i = spec.size(); - while (--i) - { - if (is_separator(spec[i])) - break; -#ifdef MSWINDOWS - // on windoze you can say a:fred.txt so the colon separates the path from the filename - else if (i == 1 && spec[i] == ':') - break; -#endif - } - bool result = initialise_folder(spec.substr(0,i+1)); - m_filename = spec.substr(i+1,spec.size()-i-1); - // TODO - some error handling? - return result; - } - - bool file_specification::simplify(void) - { - // simplify the path by removing unnecessary . and .. entries - Note that zero-length entries are treated like . - for (unsigned i = 0; i < m_path.size(); ) - { - if (m_path[i].empty() || m_path[i].compare(".") == 0) - { - // found . or null - // these both mean do nothing - so simply delete this element - m_path.erase(m_path.begin()+i); - } - else if (m_path[i].compare("..") == 0) - { - // found .. - if (i == 0 && !m_relative) - { - // up from the root does nothing so can be deleted - m_path.erase(m_path.begin()+i); - i++; - } - else if (i == 0 || m_path[i-1].compare("..") == 0) - { - // the first element of a relative path or the previous element is .. then keep it - i++; - } - else - { - // otherwise delete this element and the previous one - m_path.erase(m_path.begin()+i); - m_path.erase(m_path.begin()+i-1); - i--; - } - } - // keep all other elements - else - i++; - } - // TODO - error checking? - return true; - } - - bool file_specification::make_absolute(const std::string& root) - { - // test whether already an absolute path in which case there's nothing to do - if (absolute()) return true; - // now simply call the other version of make_absolute - file_specification rootspec; - rootspec.initialise_folder(root); - return make_absolute(rootspec); - } - - bool file_specification::make_absolute(const file_specification& rootspec) - { - // test whether already an absolute path in which case there's nothing to do - if (absolute()) return true; - // initialise the result with the root and make the root absolute - file_specification result = rootspec; - result.make_absolute(); - // now append this's relative path and filename to the root's absolute path - for (unsigned i = 0; i < subpath_size(); i++) - result.add_subpath(subpath_element(i)); - result.set_file(file()); - // now the result is the absolute path, so transfer it to this - *this = result; - // and simplify to get rid of any unwanted .. or . elements - simplify(); - return true; - } - - bool file_specification::make_relative(const std::string& root) - { - // test whether already an relative path in which case there's nothing to do - if (relative()) return true; - // now simply call the other version of make_relative - file_specification rootspec; - rootspec.initialise_folder(root); - return make_relative(rootspec); - } - - bool file_specification::make_relative(const file_specification& rootspec) - { - // test whether already an relative path in which case there's nothing to do - if (relative()) return true; - // initialise the result with the root and make the root absolute - file_specification absolute_root = rootspec; - absolute_root.make_absolute(); - // now compare elements of the absolute root with elements of this to find the common path - // if the drives are different, no conversion can take place and the result must be absolute, else clear the drive - if (!path_compare(drive(), absolute_root.drive())) return true; - set_drive(""); - // first remove leading elements that are identical to the corresponding element in root - unsigned i = 0; - while(subpath_size() > 0 && - i < absolute_root.subpath_size() && - path_compare(subpath_element(0), absolute_root.subpath_element(i))) - { - subpath_erase(0); - i++; - } - // now add a .. prefix for every element in root that is different from this - while (i < absolute_root.subpath_size()) - { - m_path.insert(m_path.begin(), ".."); - i++; - } - set_relative(); - return true; - } - - std::string file_specification::image(void) const - { - std::string result = m_drive; - if (absolute()) - result += preferred_separator; - if (!m_path.empty()) - { - for (unsigned i = 0; i < m_path.size(); i++) - { - if (i != 0) result += std::string(1,preferred_separator); - result += m_path[i]; - } - } - else if (relative()) - result += '.'; - // add a trailing / to the last directory element - if (result.empty() || !is_separator(result[result.size()-1])) - result += preferred_separator; - if (!m_filename.empty()) - result += m_filename; - return result; - } - -//////////////////////////////////////////////////////////////////////////////// -// classifying functions - -#ifdef MSWINDOWS -// file type tests are not defined for some reason on Windows despite them providing the stat() function! -#define R_OK 4 -#define W_OK 2 -#endif - - bool is_present (const std::string& thing) - { - // strip off any trailing separator because that will cause the stat function to fail - std::string path = thing; - if (!path.empty() && is_separator(path[path.size()-1])) - path.erase(path.size()-1,1); - // now test if this thing exists using the built-in stat function - struct stat buf; - return stat(path.c_str(), &buf) == 0; - } - - bool is_folder (const std::string& thing) - { - // strip off any trailing separator because that will cause the stat function to fail - std::string path = thing; - if (!path.empty() && is_separator(path[path.size()-1])) - path.erase(path.size()-1,1); - // now test if this thing exists using the built-in stat function and if so, is it a folder - struct stat buf; - if (!(stat(path.c_str(), &buf) == 0)) {return false;} - return (buf.st_mode & S_IFDIR) != 0; - } - - bool is_file (const std::string& thing) - { - // strip off any trailing separator because that will cause the stat function to fail - std::string path = thing; - if (!path.empty() && is_separator(path[path.size()-1])) - path.erase(path.size()-1,1); - // now test if this thing exists using the built-in stat function and if so, is it a file - struct stat buf; - if (!(stat(path.c_str(), &buf) == 0)) {return false;} - return (buf.st_mode & S_IFREG) != 0; - } - -//////////////////////////////////////////////////////////////////////////////// -// file functions - - bool file_exists (const std::string& filespec) - { - return is_file(filespec); - } - - bool file_readable (const std::string& filespec) - { - // a file is readable if it exists and can be read - if (!file_exists(filespec)) return false; - return access(filespec.c_str(),R_OK)==0; - } - - bool file_writable (const std::string& filespec) - { - // a file is writable if it exists as a file and is writable or if it doesn't exist but could be created and would be writable - if (is_present(filespec)) - { - if (!is_file(filespec)) return false; - return access(filespec.c_str(),W_OK)==0; - } - std::string dir = folder_part(filespec); - if (dir.empty()) dir = "."; - return folder_writable(dir); - } - - size_t file_size (const std::string& filespec) - { - struct stat buf; - if (!(stat(filespec.c_str(), &buf) == 0)) return 0; - return buf.st_size; - } - - bool file_delete (const std::string& filespec) - { - if (!is_file(filespec)) return false; - return remove(filespec.c_str())==0; - } - - bool file_rename (const std::string& old_filespec, const std::string& new_filespec) - { - if (!is_file(old_filespec)) return false; - return rename(old_filespec.c_str(), new_filespec.c_str())==0; - } - - bool file_copy (const std::string& old_filespec, const std::string& new_filespec) - { - if (!is_file(old_filespec)) return false; - // do an exact copy - to do this, use binary mode - bool result = true; - FILE* old_file = fopen(old_filespec.c_str(),"rb"); - FILE* new_file = fopen(new_filespec.c_str(),"wb"); - if (!old_file) - result = false; - else if (!new_file) - result = false; - else - { - for (int byte = getc(old_file); byte != EOF; byte = getc(old_file)) - putc(byte,new_file); - } - if (old_file) fclose(old_file); - if (new_file) fclose(new_file); - return result; - } - - bool file_move (const std::string& old_filespec, const std::string& new_filespec) - { - // try to move the file by renaming - if that fails then do a copy and delete the original - if (file_rename(old_filespec, new_filespec)) - return true; - if (!file_copy(old_filespec, new_filespec)) - return false; - // I'm not sure what to do if the delete fails - is that an error? - // I've made it an error and then delete the copy so that the original state is recovered - if (file_delete(old_filespec)) - return true; - file_delete(new_filespec); - return false; - } - - time_t file_created (const std::string& filespec) - { - struct stat buf; - if (!(stat(filespec.c_str(), &buf) == 0)) return 0; - return buf.st_ctime; - } - - time_t file_modified (const std::string& filespec) - { - struct stat buf; - if (!(stat(filespec.c_str(), &buf) == 0)) return 0; - return buf.st_mtime; - } - - time_t file_accessed (const std::string& filespec) - { - struct stat buf; - if (!(stat(filespec.c_str(), &buf) == 0)) return 0; - return buf.st_atime; - } - - std::string create_filespec (const std::string& directory, const std::string& filename) - { - std::string result = directory; - // if directory is empty then no directory part will be added - // add trailing slash if the directory was specified and does not have a trailing slash - if (!result.empty() && !is_separator(result[result.size()-1])) - result += preferred_separator; - // if filename is null or empty, nothing will be added so the path is then a directory path - result += filename; - return result; - } - - std::string create_filespec (const std::string& directory, const std::string& basename, const std::string& extension) - { - return create_filespec(directory, create_filename(basename, extension)); - } - - std::string create_filename(const std::string& basename, const std::string& extension) - { - std::string name = basename; - // extension is optional - so the dot is also optional - if (!extension.empty()) - { - if (extension[0] != '.') name += '.'; - name += extension; - } - return name; - } - -//////////////////////////////////////////////////////////////////////////////// -// folder functions - - bool folder_create (const std::string& directory) - { -#ifdef MSWINDOWS - return mkdir(directory.c_str()) == 0; -#else - return mkdir(directory.c_str(), 0777) == 0; -#endif - } - - bool folder_exists (const std::string& directory) - { - return is_folder(directory); - } - - bool folder_readable (const std::string& directory) - { - // a folder is readable if it exists and has read access - std::string dir = directory; - if (dir.empty()) dir = "."; - if (!folder_exists(dir)) return false; - return access(dir.c_str(),R_OK)==0; - } - - bool folder_writable (const std::string& directory) - { - // a folder is writable if it exists and has write access - std::string dir = directory; - if (dir.empty()) dir = "."; - if (!folder_exists(dir)) return false; - return access(dir.c_str(),W_OK)==0; - } - - bool folder_delete (const std::string& directory, bool recurse) - { - std::string dir = directory; - if (dir.empty()) dir = "."; - if (!folder_exists(dir)) return false; - bool result = true; - // depth-first traversal ensures that directory contents are deleted before trying to delete the directory itself - if (recurse) - { - std::vector subdirectories = folder_subdirectories(dir); - for (std::vector::size_type d = 0; d < subdirectories.size(); ++d) - if (!folder_delete(folder_down(dir,subdirectories[d]),true)) - result = false; - std::vector files = folder_files(dir); - for (std::vector::size_type f = 0; f < files.size(); ++f) - if (!file_delete(create_filespec(dir, files[f]))) - result = false; - } - if (rmdir(dir.c_str())!=0) result = false; - return result; - } - - bool folder_rename (const std::string& old_directory, const std::string& new_directory) - { - if (!folder_exists(old_directory)) return false; - return rename(old_directory.c_str(), new_directory.c_str())==0; - } - - bool folder_empty(const std::string& directory) - { - std::string dir = directory.empty() ? std::string(".") : directory; - bool result = true; -#ifdef MSWINDOWS - std::string wildcard = create_filespec(dir, "*.*"); - long handle = -1; - _finddata_t fileinfo; - for (bool OK = (handle = _findfirst((char*)wildcard.c_str(), &fileinfo)) != -1; OK; OK = (_findnext(handle, &fileinfo)==0)) - { - std::string strentry = fileinfo.name; - if (strentry.compare(".")!=0 && strentry.compare("..")!=0) - { - result = false; - break; - } - } - _findclose(handle); -#else - DIR* d = opendir(dir.c_str()); - if (d) - { - for (dirent* entry = readdir(d); entry; entry = readdir(d)) - { - std::string strentry = entry->d_name; - if (strentry.compare(".")!=0 && strentry.compare("..")!=0) - { - result = false; - break; - } - } - closedir(d); - } -#endif - return result; - } - - bool folder_set_current(const std::string& folder) - { - if (!folder_exists(folder)) - return false; -#ifdef MSWINDOWS - // Windose implementation - this returns non-zero for success - return (SetCurrentDirectoryA(folder.c_str()) != 0); -#else - // Unix implementation - this returns zero for success - return (chdir(folder.c_str()) == 0); -#endif - } - - std::string folder_current (void) - { - return "."; - } - - std::string folder_current_full(void) - { - // It's not clear from the documentation whether the buffer for a path should be one byte longer - // than the maximum path length to allow for the null termination, so I have made it so anyway -#ifdef MSWINDOWS - char abspath [MAX_PATH+1]; - return std::string(_fullpath(abspath, ".", MAX_PATH+1)); -#else - char pathname [MAXPATHLEN+1]; - getcwd(pathname,MAXPATHLEN+1); - return std::string(pathname); -#endif - } - - std::string folder_down (const std::string& directory, const std::string& subdirectory) - { - file_specification spec; - spec.initialise_folder(directory); - spec.add_subpath(subdirectory); - return spec.image(); - } - - std::string folder_up (const std::string& directory, unsigned levels) - { - file_specification spec; - spec.initialise_folder(directory); - for (unsigned i = 0; i < levels; i++) - spec.add_subpath(".."); - spec.simplify(); - return spec.image(); - } - - std::vector folder_subdirectories (const std::string& directory) - { - return folder_wildcard(directory, "*", true, false); - } - - std::vector folder_files (const std::string& directory) - { - return folder_wildcard(directory, "*", false, true); - } - - std::vector folder_all(const std::string& directory) - { - return folder_wildcard(directory, "*", true, true); - } - - std::vector folder_wildcard (const std::string& directory, const std::string& wild, bool subdirs, bool files) - { - std::string dir = directory.empty() ? std::string(".") : directory; - std::vector results; -#ifdef MSWINDOWS - std::string wildcard = create_filespec(dir, wild); - long handle = -1; - _finddata_t fileinfo; - for (bool OK = (handle = _findfirst((char*)wildcard.c_str(), &fileinfo)) != -1; OK; OK = (_findnext(handle, &fileinfo)==0)) - { - std::string strentry = fileinfo.name; - if (strentry.compare(".")!=0 && strentry.compare("..")!=0) - if ((subdirs && (fileinfo.attrib & _A_SUBDIR)) || (files && !(fileinfo.attrib & _A_SUBDIR))) - results.push_back(strentry); - } - _findclose(handle); -#else - DIR* d = opendir(dir.c_str()); - if (d) - { - for (dirent* entry = readdir(d); entry; entry = readdir(d)) - { - std::string strentry = entry->d_name; - if (strentry.compare(".")!=0 && strentry.compare("..")!=0) - { - std::string subpath = create_filespec(dir, strentry); - if (((subdirs && is_folder(subpath)) || (files && is_file(subpath))) && (wildcard(wild, strentry))) - results.push_back(strentry); - } - } - closedir(d); - } -#endif - return results; - } - - std::string folder_home (void) - { - if (getenv("HOME")) - return std::string(getenv("HOME")); -#ifdef MSWINDOWS - if (getenv("HOMEDRIVE") || getenv("HOMEPATH")) - return std::string(getenv("HOMEDRIVE")) + std::string(getenv("HOMEPATH")); - return "C:\\"; -#else - if (getenv("USER")) - return folder_down("/home", std::string(getenv("USER"))); - if (getenv("USERNAME")) - return folder_down("/home", std::string(getenv("USERNAME"))); - return ""; -#endif - } - -//////////////////////////////////////////////////////////////////////////////// -// path functions convert between full and relative paths - - bool is_full_path(const std::string& path) - { - file_specification spec; - spec.initialise_folder(path.empty() ? std::string(".") : path); - return spec.absolute(); - } - - bool is_relative_path(const std::string& path) - { - file_specification spec; - spec.initialise_folder(path.empty() ? std::string(".") : path); - return spec.relative(); - } - - static std::string full_path(const std::string& root, const std::string& path) - { - // convert path to a full path using root as the start point for relative paths - // decompose the path and test whether it is already an absolute path, in which case just return it - file_specification spec; - spec.initialise_folder(path.empty() ? std::string(".") : path); - if (spec.absolute()) return spec.image(); - // okay, so the path is relative after all, so we need to combine it with the root path - // decompose the root path and check whether it is relative - file_specification rootspec; - rootspec.initialise_folder(root.empty() ? std::string(".") : root); - if (rootspec.relative()) - rootspec.make_absolute(); - // Now do the conversion of the path relative to the root - spec.make_absolute(rootspec); - return spec.image(); - } - - static std::string relative_path(const std::string& root, const std::string& path) - { - // convert path to a relative path, using the root path as its starting point - // first convert both paths to full paths relative to CWD - file_specification rootspec; - rootspec.initialise_folder(root.empty() ? std::string(".") : root); - if (rootspec.relative()) - rootspec.make_absolute(); - file_specification spec; - spec.initialise_folder(path.empty() ? std::string(".") : path); - if (spec.relative()) - spec.make_absolute(); - // now make path spec relative to the root spec - spec.make_relative(rootspec); - return spec.image(); - } - - std::string folder_to_path (const std::string& path, const std::string& directory) - { - return full_path(path, directory); - } - - std::string filespec_to_path (const std::string& path, const std::string& spec) - { - return create_filespec(folder_to_path(path, folder_part(spec)),filename_part(spec)); - } - - std::string folder_to_path(const std::string& folder) - { - return folder_to_path(folder_current(), folder); - } - - std::string filespec_to_path(const std::string& filespec) - { - return filespec_to_path(folder_current(), filespec); - } - - std::string folder_to_relative_path(const std::string& root, const std::string& folder) - { - return relative_path(root, folder); - } - - std::string filespec_to_relative_path(const std::string& root, const std::string& spec) - { - return create_filespec(folder_to_relative_path(root, folder_part(spec)),filename_part(spec)); - } - - std::string folder_to_relative_path(const std::string& folder) - { - return folder_to_relative_path(folder_current(), folder); - } - - std::string filespec_to_relative_path(const std::string& filespec) - { - return filespec_to_relative_path(folder_current(), filespec); - } - - std::string folder_append_separator(const std::string& folder) - { - std::string result = folder; - if (result.empty() || !is_separator(result[result.size()-1])) - result += preferred_separator; - return result; - } - -//////////////////////////////////////////////////////////////////////////////// - - std::string basename_part (const std::string& spec) - { - std::string fname = filename_part(spec); - // scan back through filename until a '.' is found and remove suffix - // the whole filename is the basename if there is no '.' - std::string::size_type i = fname.find_last_of('.'); - // observe Unix convention that a dot at the start of a filename is part of the basename, not the extension - if (i != 0 && i != std::string::npos) - fname.erase(i, fname.size()-i); - return fname; - } - - std::string filename_part (const std::string& spec) - { - // scan back through filename until a preferred_separator is found and remove prefix; - // if there is no preferred_separator then remove nothing, i.e. the whole filespec is filename - unsigned i = spec.size(); - while (i--) - { - if (is_separator(spec[i])) - return spec.substr(i+1,spec.size()-i-1); - } - return spec; - } - - std::string extension_part (const std::string& spec) - { - std::string fname = filename_part(spec); - // scan back through filename until a '.' is found and remove prefix; - std::string::size_type i = fname.find_last_of('.'); - // observe Unix convention that a dot at the start of a filename is part of the name, not the extension; - if (i != 0 && i != std::string::npos) - fname.erase(0, i+1); - else - fname.erase(); - return fname; - } - - std::string folder_part (const std::string& spec) - { - // scan back through filename until a separator is found and remove prefix - // if there is no separator, remove the whole - unsigned i = spec.size(); - while (i--) - { - if (is_separator(spec[i])) - return spec.substr(0,i); - } - return std::string(); - } - - std::vector filespec_elements(const std::string& filespec) - { - file_specification spec; - spec.initialise_file(filespec); - std::vector result = spec.path(); - if (!spec.drive().empty()) result.insert(result.begin(),spec.drive()); - if (!spec.file().empty()) result.push_back(spec.file()); - return result; - } - - std::vector folder_elements(const std::string& folder) - { - file_specification spec; - spec.initialise_folder(folder); - std::vector result = spec.path(); - if (!spec.drive().empty()) result.insert(result.begin(),spec.drive()); - return result; - } - -//////////////////////////////////////////////////////////////////////////////// -// mimic the command lookup used by the shell - -// Windows looks at the following locations: -// 1) application root -// 2) current directory -// 3) 32-bit system directory -// 4) 16-bit system directory -// 5) windows system directory -// 6) %path% -// currently only (2) and (6) has been implemented although many system folders are on the path anyway -// also implement the implied .exe extension on commands with no path (see CreateProcess documentation) -// TODO - PATHEXT handling to find non-exe executables - - std::string path_lookup (const std::string& command) - { - std::string path = std::string(".") + PATH_SPLITTER + getenv("PATH"); - return lookup(command, path); - } - - std::string lookup (const std::string& command, const std::string& path, const std::string& splitter) - { - // first check whether the command is already a path and check whether it exists - if (!folder_part(command).empty()) - { - if (file_exists(command)) - return command; - } - else - { - // command is just a name - so do path lookup - // split the path into its elements - std::vector paths; - if (!path.empty()) - { - for(std::string::size_type offset = 0;;) - { - std::string::size_type found = path.find(splitter, offset); - if (found != std::string::npos) - { - paths.push_back(path.substr(offset, found-offset)); - offset = found + splitter.size(); - } - else - { - paths.push_back(path.substr(offset, path.size()-offset)); - break; - } - } - } - // now lookup each path to see if it its the matching one - for (unsigned i = 0; i < paths.size(); i++) - { - std::string spec = create_filespec(paths[i], command); - if (file_exists(spec)) - { - return spec; - } - } - } -#ifdef MSWINDOWS - // if there is no extension, try recursing on each possible extension - // TODO iterate through PATHEXT - if (extension_part(command).empty()) - return lookup(create_filespec(folder_part(command), basename_part(command), "exe"), path, splitter); -#endif - // if path lookup failed, return empty string to indicate error - return std::string(); - } - -//////////////////////////////////////////////////////////////////////////////// - - std::string install_path(const std::string& argv0) - { - std::string bin_directory = folder_part(argv0); - if (bin_directory.empty()) - { - // do path lookup to find the executable path - bin_directory = folder_part(path_lookup(argv0)); - } - return bin_directory; - } - -//////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// This is a portable interface to the file system. + +// The idea is that you write all file system access code using these functions, +// which are ported to all platforms that we are interested in. Therefore your +// code is inherently portable. + +// Native Windows version: switched on by macro _WIN32 which is defined by VC++/Borland/Mingw compilers +// Unix/Gnu version: default variant, no compiler directives are required but _WIN32 must be absent +// Cygwin/Gnu version: as Unix version but with additional support for Windows drive letters + +//////////////////////////////////////////////////////////////////////////////// +#include "file_system.hpp" +#include "wildcard.hpp" +#include +#include +#include +#include +#include + +#ifdef MSWINDOWS +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + +//////////////////////////////////////////////////////////////////////////////// +// definitions of separators + +#ifdef MSWINDOWS + static const char* separator_set = "\\/"; + static const char preferred_separator = '\\'; +#else + static const char* separator_set = "/"; + static const char preferred_separator = '/'; +#endif + + static bool is_separator (char ch) + { + for (int i = 0; separator_set[i]; i++) + { + if (separator_set[i] == ch) + return true; + } + return false; + } + +//////////////////////////////////////////////////////////////////////////////// +// implement string comparison of paths - Unix is case-sensitive, Windoze is case-insensitive + +#ifdef MSWINDOWS + + static std::string lowercase(const std::string& val) + { + std::string text = val; + for (unsigned i = 0; i < text.size(); i++) + text[i] = tolower(text[i]); + return text; + } + +#endif + + bool path_compare(const std::string& l, const std::string& r) + { +#ifdef MSWINDOWS + return lowercase(l) == lowercase(r); +#else + return l == r; +#endif + } + +//////////////////////////////////////////////////////////////////////////////// +// Internal data structure used to hold the different parts of a filespec + + class file_specification + { + private: + bool m_relative; // true = relative, false = absolute + std::string m_drive; // drive - drive letter (e.g. "c:") or the path for an UNC (e.g. "\\somewhere") + // empty if not known or on Unix + std::vector m_path; // the subdirectory path to follow from the drive + std::string m_filename; // the filename + public: + file_specification(void) : m_relative(false) {} + ~file_specification(void) {} + + bool initialise_folder(const std::string& spec); + bool initialise_file(const std::string& spec); + bool simplify(void); + bool make_absolute(const std::string& root = folder_current_full()); + bool make_absolute(const file_specification& root); + bool make_relative(const std::string& root = folder_current_full()); + bool make_relative(const file_specification& root); + bool relative(void) const {return m_relative;} + bool absolute(void) const {return !relative();} + void set_relative(void) {m_relative = true;} + void set_absolute(void) {m_relative = false;} + + const std::string& drive(void) const {return m_drive;} + std::string& drive(void) {return m_drive;} + void set_drive(const std::string& drive) {m_drive = drive;} + + const std::vector& path(void) const {return m_path;} + std::vector& path(void) {return m_path;} + void set_path(const std::vector& path) {m_path = path;} + + void add_subpath(const std::string& subpath) {m_path.push_back(subpath);} + unsigned subpath_size(void) const {return m_path.size();} + const std::string& subpath_element(unsigned i) const {return m_path[i];} + void subpath_erase(unsigned i) {m_path.erase(m_path.begin()+i);} + + const std::string& file(void) const {return m_filename;} + std::string& file(void) {return m_filename;} + void set_file(const std::string& file) {m_filename = file;} + + std::string image(void) const; + }; + + bool file_specification::initialise_folder(const std::string& folder_spec) + { + std::string spec = folder_spec; + m_relative = true; + m_drive.erase(); + m_path.clear(); + m_filename.erase(); + unsigned i = 0; +#ifdef MSWINDOWS + // first split off the drive letter or UNC prefix on Windows + if (spec.size() >= 2 && isalpha(spec[0]) && spec[1] == ':') + { + // found a drive letter + i = 2; + m_drive = spec.substr(0, 2); + m_relative = false; + // if there is a drive but no path or a relative path, get the current + // path for this drive and prepend it to the path + if (i == spec.size() || !is_separator(spec[i])) + { + // getdcwd requires the drive number (1..26) not the letter (A..Z) + char path [MAX_PATH+1]; + int drivenum = toupper(m_drive[0]) - 'A' + 1; + if (_getdcwd(drivenum, path, MAX_PATH+1)) + { + // the path includes the drive so we have the drive info twice + // need to prepend this absolute path to the spec such that any remaining relative path is still retained + if (!is_separator(path[strlen(path)-1])) spec.insert(2, 1, preferred_separator); + spec.insert(2, path+2); + } + else + { + // non-existent drive - fill in just the root directory + spec.insert(2, 1, preferred_separator); + } + } + } + else if (spec.size() >= 2 && is_separator(spec[0]) && is_separator(spec[1])) + { + // found an UNC prefix + i = 2; + // find the end of the prefix by scanning for the next seperator or the end of the spec + while (i < spec.size() && !is_separator(spec[i])) i++; + m_drive = spec.substr(0, i); + m_relative = false; + } +#endif +#ifdef CYGWIN + // first split off the drive letter or UNC prefix on Windows - the Cygwin environment supports these too + if (spec.size() >= 2 && isalpha(spec[0]) && spec[1] == ':') + { + // found a drive letter + i = 2; + m_drive = spec.substr(0, 2); + m_relative = false; + // if there is a drive but no path or a relative path, get the current + // path for this drive and prepend it to the path + if (i == spec.size() || !is_separator(spec[i])) + { + // non-existent drive - fill in just the root directory + spec.insert(2, 1, preferred_separator); + } + } + else if (spec.size() >= 2 && is_separator(spec[0]) && is_separator(spec[1])) + { + // found an UNC prefix + i = 2; + // find the end of the prefix by scanning for the next seperator or the end of the spec + while (i < spec.size() && !is_separator(spec[i])) i++; + m_drive = spec.substr(0, i); + m_relative = false; + } +#endif + // check whether the path is absolute or relative and discard the leading / if absolute + if (i < spec.size() && is_separator(spec[i])) + { + m_relative = false; + i++; +#ifdef MSWINDOWS + // if there's no drive, fill it in on Windows since absolute paths must have a drive + if (m_drive.empty()) + { + m_drive += (char)(_getdrive() - 1 + 'A'); + m_drive += ':'; + } +#endif + } + // now extract the path elements - note that a trailing / is not significant since /a/b/c/ === /a/b/c + // also note that the leading / has been discarded - all paths are relative + // if absolute() is set, then paths are relative to the drive, else they are relative to the current path + unsigned start = i; + while(i <= spec.size()) + { + if (i == spec.size()) + { + // path element terminated by the end of the string + // discard this element if it is zero length because that represents the trailing / + if (i != start) + m_path.push_back(spec.substr(start, i-start)); + } + else if (is_separator(spec[i])) + { + // path element terminated by a separator + m_path.push_back(spec.substr(start, i-start)); + start = i+1; + } + i++; + } + // TODO - some error handling? + return true; + } + + bool file_specification::initialise_file(const std::string& spec) + { + m_filename.erase(); + // remove last element as the file and then treat the rest as a folder + unsigned i = spec.size(); + while (--i) + { + if (is_separator(spec[i])) + break; +#ifdef MSWINDOWS + // on windoze you can say a:fred.txt so the colon separates the path from the filename + else if (i == 1 && spec[i] == ':') + break; +#endif + } + bool result = initialise_folder(spec.substr(0,i+1)); + m_filename = spec.substr(i+1,spec.size()-i-1); + // TODO - some error handling? + return result; + } + + bool file_specification::simplify(void) + { + // simplify the path by removing unnecessary . and .. entries - Note that zero-length entries are treated like . + for (unsigned i = 0; i < m_path.size(); ) + { + if (m_path[i].empty() || m_path[i].compare(".") == 0) + { + // found . or null + // these both mean do nothing - so simply delete this element + m_path.erase(m_path.begin()+i); + } + else if (m_path[i].compare("..") == 0) + { + // found .. + if (i == 0 && !m_relative) + { + // up from the root does nothing so can be deleted + m_path.erase(m_path.begin()+i); + i++; + } + else if (i == 0 || m_path[i-1].compare("..") == 0) + { + // the first element of a relative path or the previous element is .. then keep it + i++; + } + else + { + // otherwise delete this element and the previous one + m_path.erase(m_path.begin()+i); + m_path.erase(m_path.begin()+i-1); + i--; + } + } + // keep all other elements + else + i++; + } + // TODO - error checking? + return true; + } + + bool file_specification::make_absolute(const std::string& root) + { + // test whether already an absolute path in which case there's nothing to do + if (absolute()) return true; + // now simply call the other version of make_absolute + file_specification rootspec; + rootspec.initialise_folder(root); + return make_absolute(rootspec); + } + + bool file_specification::make_absolute(const file_specification& rootspec) + { + // test whether already an absolute path in which case there's nothing to do + if (absolute()) return true; + // initialise the result with the root and make the root absolute + file_specification result = rootspec; + result.make_absolute(); + // now append this's relative path and filename to the root's absolute path + for (unsigned i = 0; i < subpath_size(); i++) + result.add_subpath(subpath_element(i)); + result.set_file(file()); + // now the result is the absolute path, so transfer it to this + *this = result; + // and simplify to get rid of any unwanted .. or . elements + simplify(); + return true; + } + + bool file_specification::make_relative(const std::string& root) + { + // test whether already an relative path in which case there's nothing to do + if (relative()) return true; + // now simply call the other version of make_relative + file_specification rootspec; + rootspec.initialise_folder(root); + return make_relative(rootspec); + } + + bool file_specification::make_relative(const file_specification& rootspec) + { + // test whether already an relative path in which case there's nothing to do + if (relative()) return true; + // initialise the result with the root and make the root absolute + file_specification absolute_root = rootspec; + absolute_root.make_absolute(); + // now compare elements of the absolute root with elements of this to find the common path + // if the drives are different, no conversion can take place and the result must be absolute, else clear the drive + if (!path_compare(drive(), absolute_root.drive())) return true; + set_drive(""); + // first remove leading elements that are identical to the corresponding element in root + unsigned i = 0; + while(subpath_size() > 0 && + i < absolute_root.subpath_size() && + path_compare(subpath_element(0), absolute_root.subpath_element(i))) + { + subpath_erase(0); + i++; + } + // now add a .. prefix for every element in root that is different from this + while (i < absolute_root.subpath_size()) + { + m_path.insert(m_path.begin(), ".."); + i++; + } + set_relative(); + return true; + } + + std::string file_specification::image(void) const + { + std::string result = m_drive; + if (absolute()) + result += preferred_separator; + if (!m_path.empty()) + { + for (unsigned i = 0; i < m_path.size(); i++) + { + if (i != 0) result += std::string(1,preferred_separator); + result += m_path[i]; + } + } + else if (relative()) + result += '.'; + // add a trailing / to the last directory element + if (result.empty() || !is_separator(result[result.size()-1])) + result += preferred_separator; + if (!m_filename.empty()) + result += m_filename; + return result; + } + +//////////////////////////////////////////////////////////////////////////////// +// classifying functions + +// Under both Windows and Unix, the stat function is used for classification + +// Under Linux, the following classifications are defined +// source: Linux man page for stat(2) http://linux.die.net/man/2/stat +// S_IFMT 0170000 bitmask for the file type bitfields +// S_IFSOCK 0140000 socket (Note this overlaps with S_IFDIR) +// S_IFLNK 0120000 symbolic link +// S_IFREG 0100000 regular file +// S_IFBLK 0060000 block device +// S_IFDIR 0040000 directory +// S_IFCHR 0020000 character device +// S_IFIFO 0010000 FIFO +// There are also some Posix-standard macros: +// S_ISREG(m) is it a regular file? +// S_ISDIR(m) directory? +// S_ISCHR(m) character device? +// S_ISBLK(m) block device? +// S_ISFIFO(m) FIFO (named pipe)? +// S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.) +// S_ISSOCK(m) socket? (Not in POSIX.1-1996.) +// Under Windows, the following are defined: +// source: Header file sys/stat.h distributed with Visual Studio 10 +// _S_IFMT (S_IFMT) 0xF000 file type mask +// _S_IFREG (S_IFREG) 0x8000 regular +// _S_IFDIR (S_IFDIR) 0x4000 directory +// _S_IFCHR (S_IFCHR) 0x2000 character special +// _S_IFIFO 0x1000 pipe + +#ifdef MSWINDOWS +// file type tests are not defined for some reason on Windows despite them providing the stat() function! +#define R_OK 4 +#define W_OK 2 +// Posix-style macros for Windows +#ifndef S_ISREG +#define S_ISREG(mode) ((mode & _S_IFMT) == _S_IFREG) +#endif +#ifndef S_ISDIR +#define S_ISDIR(mode) ((mode & _S_IFMT) == _S_IFDIR) +#endif +#ifndef S_ISCHR +#define S_ISCHR(mode) ((mode & _S_IFMT) == _S_IFCHR) +#endif +#ifndef S_ISBLK +#define S_ISBLK(mode) (false) +#endif +#ifndef S_ISFIFO +#define S_ISFIFO(mode) ((mode & _S_IFMT) == _S_IFIFO) +#endif +#ifndef S_ISLNK +#define S_ISLNK(mode) (false) +#endif +#ifndef S_ISSOCK +#define S_ISSOCK(mode) (false) +#endif +#endif + + bool is_present (const std::string& thing) + { + // strip off any trailing separator because that will cause the stat function to fail + std::string path = thing; + if (!path.empty() && is_separator(path[path.size()-1])) + path.erase(path.size()-1,1); + // now test if this thing exists using the built-in stat function + struct stat buf; + return stat(path.c_str(), &buf) == 0; + } + + bool is_folder (const std::string& thing) + { + // strip off any trailing separator because that will cause the stat function to fail + std::string path = thing; + if (!path.empty() && is_separator(path[path.size()-1])) + path.erase(path.size()-1,1); + // now test if this thing exists using the built-in stat function and if so, is it a folder + struct stat buf; + if (!(stat(path.c_str(), &buf) == 0)) + return false; + // If the object is present, see if it is a directory + // this is the Posix-approved way of testing + return S_ISDIR(buf.st_mode); + } + + bool is_file (const std::string& thing) + { + // strip off any trailing separator because that will cause the stat function to fail + std::string path = thing; + if (!path.empty() && is_separator(path[path.size()-1])) + path.erase(path.size()-1,1); + // now test if this thing exists using the built-in stat function and if so, is it a file + struct stat buf; + if (!(stat(path.c_str(), &buf) == 0)) + return false; + // If the object is present, see if it is a file or file-like object + // Note that devices are neither folders nor files + // this is the Posix-approved way of testing + return S_ISREG(buf.st_mode) || S_ISLNK(buf.st_mode) || S_ISSOCK(buf.st_mode) || S_ISFIFO(buf.st_mode); + } + +//////////////////////////////////////////////////////////////////////////////// +// file functions + + bool file_exists (const std::string& filespec) + { + return is_file(filespec); + } + + bool file_readable (const std::string& filespec) + { + // a file is readable if it exists and can be read + if (!file_exists(filespec)) return false; + return access(filespec.c_str(),R_OK)==0; + } + + bool file_writable (const std::string& filespec) + { + // a file is writable if it exists as a file and is writable or if + // it doesn't exist but could be created and would be writable + if (is_present(filespec)) + { + if (!is_file(filespec)) return false; + return access(filespec.c_str(),W_OK)==0; + } + std::string dir = folder_part(filespec); + if (dir.empty()) dir = "."; + return folder_writable(dir); + } + + size_t file_size (const std::string& filespec) + { + struct stat buf; + if (!(stat(filespec.c_str(), &buf) == 0)) return 0; + return buf.st_size; + } + + bool file_delete (const std::string& filespec) + { + if (!is_file(filespec)) return false; + return remove(filespec.c_str())==0; + } + + bool file_rename (const std::string& old_filespec, const std::string& new_filespec) + { + if (!is_file(old_filespec)) return false; + return rename(old_filespec.c_str(), new_filespec.c_str())==0; + } + + bool file_copy (const std::string& old_filespec, const std::string& new_filespec) + { + if (!is_file(old_filespec)) return false; + // do an exact copy - to do this, use binary mode + bool result = true; + FILE* old_file = fopen(old_filespec.c_str(),"rb"); + FILE* new_file = fopen(new_filespec.c_str(),"wb"); + if (!old_file) + result = false; + else if (!new_file) + result = false; + else + { + for (int byte = getc(old_file); byte != EOF; byte = getc(old_file)) + putc(byte,new_file); + } + if (old_file) fclose(old_file); + if (new_file) fclose(new_file); + return result; + } + + bool file_move (const std::string& old_filespec, const std::string& new_filespec) + { + // try to move the file by renaming - if that fails then do a copy and delete the original + if (file_rename(old_filespec, new_filespec)) + return true; + if (!file_copy(old_filespec, new_filespec)) + return false; + // I'm not sure what to do if the delete fails - is that an error? + // I've made it an error and then delete the copy so that the original state is recovered + if (file_delete(old_filespec)) + return true; + file_delete(new_filespec); + return false; + } + + time_t file_created (const std::string& filespec) + { + struct stat buf; + if (!(stat(filespec.c_str(), &buf) == 0)) return 0; + return buf.st_ctime; + } + + time_t file_modified (const std::string& filespec) + { + struct stat buf; + if (!(stat(filespec.c_str(), &buf) == 0)) return 0; + return buf.st_mtime; + } + + time_t file_accessed (const std::string& filespec) + { + struct stat buf; + if (!(stat(filespec.c_str(), &buf) == 0)) return 0; + return buf.st_atime; + } + + std::string create_filespec (const std::string& directory, const std::string& filename) + { + std::string result = directory; + // if directory is empty then no directory part will be added + // add trailing slash if the directory was specified and does not have a trailing slash + if (!result.empty() && !is_separator(result[result.size()-1])) + result += preferred_separator; + // if filename is null or empty, nothing will be added so the path is then a directory path + result += filename; + return result; + } + + std::string create_filespec (const std::string& directory, const std::string& basename, const std::string& extension) + { + return create_filespec(directory, create_filename(basename, extension)); + } + + std::string create_filename(const std::string& basename, const std::string& extension) + { + std::string name = basename; + // extension is optional - so the dot is also optional + if (!extension.empty()) + { + if (extension[0] != '.') name += '.'; + name += extension; + } + return name; + } + +//////////////////////////////////////////////////////////////////////////////// +// folder functions + + bool folder_create (const std::string& directory) + { +#ifdef MSWINDOWS + return mkdir(directory.c_str()) == 0; +#else + return mkdir(directory.c_str(), 0777) == 0; +#endif + } + + bool folder_exists (const std::string& directory) + { + return is_folder(directory); + } + + bool folder_readable (const std::string& directory) + { + // a folder is readable if it exists and has read access + std::string dir = directory; + if (dir.empty()) dir = "."; + if (!folder_exists(dir)) return false; + return access(dir.c_str(),R_OK)==0; + } + + bool folder_writable (const std::string& directory) + { + // a folder is writable if it exists and has write access + std::string dir = directory; + if (dir.empty()) dir = "."; + if (!folder_exists(dir)) return false; + return access(dir.c_str(),W_OK)==0; + } + + bool folder_delete (const std::string& directory, bool recurse) + { + std::string dir = directory; + if (dir.empty()) dir = "."; + if (!folder_exists(dir)) return false; + bool result = true; + // depth-first traversal ensures that directory contents are deleted before trying to delete the directory itself + if (recurse) + { + std::vector subdirectories = folder_subdirectories(dir); + for (std::vector::size_type d = 0; d < subdirectories.size(); ++d) + if (!folder_delete(folder_down(dir,subdirectories[d]),true)) + result = false; + std::vector files = folder_files(dir); + for (std::vector::size_type f = 0; f < files.size(); ++f) + if (!file_delete(create_filespec(dir, files[f]))) + result = false; + } + if (rmdir(dir.c_str())!=0) result = false; + return result; + } + + bool folder_rename (const std::string& old_directory, const std::string& new_directory) + { + if (!folder_exists(old_directory)) return false; + return rename(old_directory.c_str(), new_directory.c_str())==0; + } + + bool folder_empty(const std::string& directory) + { + std::string dir = directory.empty() ? std::string(".") : directory; + bool result = true; +#ifdef MSWINDOWS + std::string wildcard = create_filespec(dir, "*.*"); + long handle = -1; + _finddata_t fileinfo; + for (bool OK = (handle = _findfirst((char*)wildcard.c_str(), &fileinfo)) != -1; OK; OK = (_findnext(handle, &fileinfo)==0)) + { + std::string strentry = fileinfo.name; + if (strentry.compare(".")!=0 && strentry.compare("..")!=0) + { + result = false; + break; + } + } + _findclose(handle); +#else + DIR* d = opendir(dir.c_str()); + if (d) + { + for (dirent* entry = readdir(d); entry; entry = readdir(d)) + { + std::string strentry = entry->d_name; + if (strentry.compare(".")!=0 && strentry.compare("..")!=0) + { + result = false; + break; + } + } + closedir(d); + } +#endif + return result; + } + + bool folder_set_current(const std::string& folder) + { + if (!folder_exists(folder)) + return false; +#ifdef MSWINDOWS + // Windose implementation - this returns non-zero for success + return (SetCurrentDirectoryA(folder.c_str()) != 0); +#else + // Unix implementation - this returns zero for success + return (chdir(folder.c_str()) == 0); +#endif + } + + std::string folder_current (void) + { + return "."; + } + + std::string folder_current_full(void) + { + // It's not clear from the documentation whether the buffer for a path should be one byte longer + // than the maximum path length to allow for the null termination, so I have made it so anyway +#ifdef MSWINDOWS + char abspath [MAX_PATH+1]; + return std::string(_fullpath(abspath, ".", MAX_PATH+1)); +#else + char pathname [MAXPATHLEN+1]; + char* result = getcwd(pathname,MAXPATHLEN+1); + if (!result) + { + // should really report the error from errno + return std::string(); + } + return std::string(result); +#endif + } + + std::string folder_down (const std::string& directory, const std::string& subdirectory) + { + file_specification spec; + spec.initialise_folder(directory); + spec.add_subpath(subdirectory); + return spec.image(); + } + + std::string folder_up (const std::string& directory, unsigned levels) + { + file_specification spec; + spec.initialise_folder(directory); + for (unsigned i = 0; i < levels; i++) + spec.add_subpath(".."); + spec.simplify(); + return spec.image(); + } + + std::vector folder_subdirectories (const std::string& directory) + { + return folder_wildcard(directory, "*", true, false); + } + + std::vector folder_files (const std::string& directory) + { + return folder_wildcard(directory, "*", false, true); + } + + std::vector folder_all(const std::string& directory) + { + return folder_wildcard(directory, "*", true, true); + } + + std::vector folder_wildcard (const std::string& directory, const std::string& wild, bool subdirs, bool files) + { + std::string dir = directory.empty() ? std::string(".") : directory; + std::vector results; +#ifdef MSWINDOWS + std::string wildcard = create_filespec(dir, wild); + long handle = -1; + _finddata_t fileinfo; + for (bool OK = (handle = _findfirst((char*)wildcard.c_str(), &fileinfo)) != -1; OK; OK = (_findnext(handle, &fileinfo)==0)) + { + std::string strentry = fileinfo.name; + if (strentry.compare(".")!=0 && strentry.compare("..")!=0) + if ((subdirs && (fileinfo.attrib & _A_SUBDIR)) || (files && !(fileinfo.attrib & _A_SUBDIR))) + results.push_back(strentry); + } + _findclose(handle); +#else + DIR* d = opendir(dir.c_str()); + if (d) + { + for (dirent* entry = readdir(d); entry; entry = readdir(d)) + { + std::string strentry = entry->d_name; + if (strentry.compare(".")!=0 && strentry.compare("..")!=0) + { + std::string subpath = create_filespec(dir, strentry); + if (((subdirs && is_folder(subpath)) || (files && is_file(subpath))) && (wildcard(wild, strentry))) + results.push_back(strentry); + } + } + closedir(d); + } +#endif + return results; + } + + std::string folder_home (void) + { + if (getenv("HOME")) + return std::string(getenv("HOME")); +#ifdef MSWINDOWS + if (getenv("HOMEDRIVE") || getenv("HOMEPATH")) + return std::string(getenv("HOMEDRIVE")) + std::string(getenv("HOMEPATH")); + return "C:\\"; +#else + if (getenv("USER")) + return folder_down("/home", std::string(getenv("USER"))); + if (getenv("USERNAME")) + return folder_down("/home", std::string(getenv("USERNAME"))); + return ""; +#endif + } + +//////////////////////////////////////////////////////////////////////////////// +// path functions convert between full and relative paths + + bool is_full_path(const std::string& path) + { + file_specification spec; + spec.initialise_folder(path.empty() ? std::string(".") : path); + return spec.absolute(); + } + + bool is_relative_path(const std::string& path) + { + file_specification spec; + spec.initialise_folder(path.empty() ? std::string(".") : path); + return spec.relative(); + } + + static std::string full_path(const std::string& root, const std::string& path) + { + // convert path to a full path using root as the start point for relative paths + // decompose the path and test whether it is already an absolute path, in which case just return it + file_specification spec; + spec.initialise_folder(path.empty() ? std::string(".") : path); + if (spec.absolute()) return spec.image(); + // okay, so the path is relative after all, so we need to combine it with the root path + // decompose the root path and check whether it is relative + file_specification rootspec; + rootspec.initialise_folder(root.empty() ? std::string(".") : root); + if (rootspec.relative()) + rootspec.make_absolute(); + // Now do the conversion of the path relative to the root + spec.make_absolute(rootspec); + return spec.image(); + } + + static std::string relative_path(const std::string& root, const std::string& path) + { + // convert path to a relative path, using the root path as its starting point + // first convert both paths to full paths relative to CWD + file_specification rootspec; + rootspec.initialise_folder(root.empty() ? std::string(".") : root); + if (rootspec.relative()) + rootspec.make_absolute(); + file_specification spec; + spec.initialise_folder(path.empty() ? std::string(".") : path); + if (spec.relative()) + spec.make_absolute(); + // now make path spec relative to the root spec + spec.make_relative(rootspec); + return spec.image(); + } + + std::string folder_to_path (const std::string& path, const std::string& directory) + { + return full_path(path, directory); + } + + std::string filespec_to_path (const std::string& path, const std::string& spec) + { + return create_filespec(folder_to_path(path, folder_part(spec)),filename_part(spec)); + } + + std::string folder_to_path(const std::string& folder) + { + return folder_to_path(folder_current(), folder); + } + + std::string filespec_to_path(const std::string& filespec) + { + return filespec_to_path(folder_current(), filespec); + } + + std::string folder_to_relative_path(const std::string& root, const std::string& folder) + { + return relative_path(root, folder); + } + + std::string filespec_to_relative_path(const std::string& root, const std::string& spec) + { + return create_filespec(folder_to_relative_path(root, folder_part(spec)),filename_part(spec)); + } + + std::string folder_to_relative_path(const std::string& folder) + { + return folder_to_relative_path(folder_current(), folder); + } + + std::string filespec_to_relative_path(const std::string& filespec) + { + return filespec_to_relative_path(folder_current(), filespec); + } + + std::string folder_append_separator(const std::string& folder) + { + std::string result = folder; + if (result.empty() || !is_separator(result[result.size()-1])) + result += preferred_separator; + return result; + } + +//////////////////////////////////////////////////////////////////////////////// + + std::string basename_part (const std::string& spec) + { + std::string fname = filename_part(spec); + // scan back through filename until a '.' is found and remove suffix + // the whole filename is the basename if there is no '.' + std::string::size_type i = fname.find_last_of('.'); + // observe Unix convention that a dot at the start of a filename is part of the basename, not the extension + if (i != 0 && i != std::string::npos) + fname.erase(i, fname.size()-i); + return fname; + } + + std::string filename_part (const std::string& spec) + { + // scan back through filename until a preferred_separator is found and remove prefix; + // if there is no preferred_separator then remove nothing, i.e. the whole filespec is filename + unsigned i = spec.size(); + while (i--) + { + if (is_separator(spec[i])) + return spec.substr(i+1,spec.size()-i-1); + } + return spec; + } + + std::string extension_part (const std::string& spec) + { + std::string fname = filename_part(spec); + // scan back through filename until a '.' is found and remove prefix; + std::string::size_type i = fname.find_last_of('.'); + // observe Unix convention that a dot at the start of a filename is part of the name, not the extension; + if (i != 0 && i != std::string::npos) + fname.erase(0, i+1); + else + fname.erase(); + return fname; + } + + std::string folder_part (const std::string& spec) + { + // scan back through filename until a separator is found and remove prefix + // if there is no separator, remove the whole + unsigned i = spec.size(); + while (i--) + { + if (is_separator(spec[i])) + return spec.substr(0,i); + } + return std::string(); + } + + std::vector filespec_elements(const std::string& filespec) + { + file_specification spec; + spec.initialise_file(filespec); + std::vector result = spec.path(); + if (!spec.drive().empty()) result.insert(result.begin(),spec.drive()); + if (!spec.file().empty()) result.push_back(spec.file()); + return result; + } + + std::vector folder_elements(const std::string& folder) + { + file_specification spec; + spec.initialise_folder(folder); + std::vector result = spec.path(); + if (!spec.drive().empty()) result.insert(result.begin(),spec.drive()); + return result; + } + +//////////////////////////////////////////////////////////////////////////////// +// mimic the command lookup used by the shell + +// Windows looks at the following locations: +// 1) application root +// 2) current directory +// 3) 32-bit system directory +// 4) 16-bit system directory +// 5) windows system directory +// 6) %path% +// currently only (2) and (6) has been implemented although many system folders are on the path anyway +// also implement the implied .exe extension on commands with no path (see CreateProcess documentation) +// TODO - PATHEXT handling to find non-exe executables + + std::string path_lookup (const std::string& command) + { + std::string path = std::string(".") + PATH_SPLITTER + getenv("PATH"); + return lookup(command, path); + } + + std::string lookup (const std::string& command, const std::string& path, const std::string& splitter) + { + // first check whether the command is already a path and check whether it exists + if (!folder_part(command).empty()) + { + if (file_exists(command)) + return command; + } + else + { + // command is just a name - so do path lookup + // split the path into its elements + std::vector paths; + if (!path.empty()) + { + for(std::string::size_type offset = 0;;) + { + std::string::size_type found = path.find(splitter, offset); + if (found != std::string::npos) + { + paths.push_back(path.substr(offset, found-offset)); + offset = found + splitter.size(); + } + else + { + paths.push_back(path.substr(offset, path.size()-offset)); + break; + } + } + } + // now lookup each path to see if it its the matching one + for (unsigned i = 0; i < paths.size(); i++) + { + std::string spec = create_filespec(paths[i], command); + if (file_exists(spec)) + { + return spec; + } + } + } +#ifdef MSWINDOWS + // if there is no extension, try recursing on each possible extension + // TODO iterate through PATHEXT + if (extension_part(command).empty()) + return lookup(create_filespec(folder_part(command), basename_part(command), "exe"), path, splitter); +#endif + // if path lookup failed, return empty string to indicate error + return std::string(); + } + +//////////////////////////////////////////////////////////////////////////////// + + std::string install_path(const std::string& argv0) + { + std::string bin_directory = folder_part(argv0); + if (bin_directory.empty()) + { + // do path lookup to find the executable path + bin_directory = folder_part(path_lookup(argv0)); + } + return bin_directory; + } + +//////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus diff --git a/src/stlplus/portability/file_system.hpp b/src/stlplus/portability/file_system.hpp index ce33ffe..6a7d178 100644 --- a/src/stlplus/portability/file_system.hpp +++ b/src/stlplus/portability/file_system.hpp @@ -1,201 +1,202 @@ -#ifndef STLPLUS_FILE_SYSTEM -#define STLPLUS_FILE_SYSTEM -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Simplified access to the File system - -// All file system access and filename manipulation should be done -// with this package. Then it is only necessary to port this package -// to port all file handling. - -//////////////////////////////////////////////////////////////////////////////// -#include "portability_fixes.hpp" -#include -#include -#include -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // implement string comparison of paths - Unix is case-sensitive, Windows is case-insensitive - - bool path_compare(const std::string& l, const std::string& r); - - //////////////////////////////////////////////////////////////////////////////// - // classifying functions - - // test for whether there's something (i.e. folder or file) with this name - bool is_present(const std::string& thing); - // test for whether there's something present and its a folder - bool is_folder(const std::string& thing); - // test for whether there's something present and its a file - bool is_file(const std::string& thing); - - //////////////////////////////////////////////////////////////////////////////// - // file functions - - // tests whether there's a file of this name - bool file_exists(const std::string& filespec); - // tests whether the file is readable - i.e. exists and has read mode set - bool file_readable(const std::string& filespec); - // tests whether file is writable - either it exists and is writable or doesn't exist but is in a writable folder - bool file_writable(const std::string& filespec); - // the size of the file in bytes - 0 if doesn't exist - size_t file_size(const std::string& filespec); - // delete the file - returns true if the delete succeeded - bool file_delete(const std::string& filespec); - // rename the file - returns true if the rename succeeded - bool file_rename (const std::string& old_filespec, const std::string& new_filespec); - // make an exact copy of the file - returns true if it succeeded - bool file_copy (const std::string& old_filespec, const std::string& new_filespec); - // move the file - tries to rename, if that fails, tries to copy - returns true if either of these succeeded - bool file_move (const std::string& old_filespec, const std::string& new_filespec); - - // get the file's time stamps as a time_t - see the stlplus time.hpp package - - // time the file was originally created - time_t file_created(const std::string& filespec); - // time the file was last modified - time_t file_modified(const std::string& filespec); - // time the file was accessed - time_t file_accessed(const std::string& filespec); - - // platform-specific string handling to combine a directory and filename into a path - - // combine a folder with a filename (basename.extension) - std::string create_filespec(const std::string& folder, const std::string& filename); - // combine a folder, a basename and an extension - extension does not need the . - std::string create_filespec(const std::string& folder, const std::string& basename, const std::string& extension); - // combine a basename and an extension - extension does not need the . - std::string create_filename(const std::string& basename, const std::string& extension); - - //////////////////////////////////////////////////////////////////////////////// - // folder functions - - // craete a folder - returns true if successful - bool folder_create(const std::string& folder); - // tests for whether the folder exists, i.e. there is something of that name and its a folder - bool folder_exists(const std::string& folder); - // test whether the folder contents are readable - bool folder_readable(const std::string& folder); - // tests whether the folder can be written to - for example to create a new file - bool folder_writable(const std::string& folder); - // delete the folder, optionally deleting the contents first - only succeeds if everything could be deleted - bool folder_delete(const std::string& folder, bool recurse = false); - // rename the folder - this probably only works within a disk/partition - bool folder_rename (const std::string& old_directory, const std::string& new_directory); - // test whether the folder is empty (of files) - bool folder_empty(const std::string& folder); - - // set the current folder - bool folder_set_current(const std::string& folder); - - // platform-specific string handling to retrieve some special locations - // these do not check whether the folder exists, they just process strings - - // get the current folder - std::string folder_current(void); - // get the current folder as a full path - std::string folder_current_full(void); - // get the home folder - $HOME or %HOMEDRIVE%%HOMEPATH% - std::string folder_home(void); - // go down a level in the folder hierarchy - std::string folder_down(const std::string& folder, const std::string& subfolder); - // go up a level in the folder hierarchy - std::string folder_up(const std::string& folder, unsigned levels = 1); - - // get folder contents - - // the set of all subdirectories - std::vector folder_subdirectories(const std::string& folder); - // the set of all files - std::vector folder_files(const std::string& folder); - // the set of all folders and files - std::vector folder_all(const std::string& folder); - // the set of all folder contents matching a wildcard string - // if folders is true, include folders; if files is true, include files - std::vector folder_wildcard(const std::string& folder, - const std::string& wildcard, - bool folders = true, - bool files = true); - - //////////////////////////////////////////////////////////////////////////////// - // path functions - - // string manipulations of paths - - // test whether a string represents a full path or a relative one - bool is_full_path(const std::string& path); - bool is_relative_path(const std::string& path); - - // convert to a full path relative to the root path - std::string folder_to_path(const std::string& root, const std::string& folder); - std::string filespec_to_path(const std::string& root, const std::string& filespec); - - // convert to a full path relative to the current working directory - std::string folder_to_path(const std::string& folder); - std::string filespec_to_path(const std::string& filespec); - - // convert to a relative path relative to the root path - std::string folder_to_relative_path(const std::string& root, const std::string& folder); - std::string filespec_to_relative_path(const std::string& root, const std::string& filespec); - - // convert to a relative path relative to the current working directory - std::string folder_to_relative_path(const std::string& folder); - std::string filespec_to_relative_path(const std::string& filespec); - - // append a folder separator to the path to make it absolutely clear that it is a folder - std::string folder_append_separator(const std::string& folder); - - //////////////////////////////////////////////////////////////////////////////// - // access functions split a filespec into its elements - - // get the basename - that is, the name of the file without folder or extension parts - std::string basename_part(const std::string& filespec); - // get the filename - that is, the name of the file without folder part but with extension - std::string filename_part(const std::string& filespec); - // get the extension - that is the part of the filename after the . (and excluding the .) - std::string extension_part(const std::string& filespec); - // get the folder part - that is the filespec with the filename removed - std::string folder_part(const std::string& filespec); - - // split a path into a vector of elements - i.e. split at the folder separator - std::vector folder_elements(const std::string& folder); - std::vector filespec_elements(const std::string& filespec); - - //////////////////////////////////////////////////////////////////////////////// - // Path lookup functions - -#ifdef MSWINDOWS -#define PATH_SPLITTER ";" -#else -#define PATH_SPLITTER ":" -#endif - - // The lookup normally carried out by the shell to find a command in a - // directory in the PATH. Give this function the name of a command and it - // will return the full path. It returns an empty string on failure. - std::string path_lookup (const std::string& command); - - // Generalised form of the above, takes a second argument - // - the list to search. This can be used to do other path lookups, - // such as LD_LIBRARY_PATH. The third argument specifies the splitter - - // the default value of PATH_SPLITTER is appropriate for environment variables. - std::string lookup (const std::string& file, const std::string& path, const std::string& splitter = PATH_SPLITTER); - - // utility function for finding the folder that contains the current executable - // the argument is the argv[0] parameter passed to main - std::string install_path(const std::string& argv0); - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#endif +#ifndef STLPLUS_FILE_SYSTEM +#define STLPLUS_FILE_SYSTEM +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Simplified access to the File system + +// All file system access and filename manipulation should be done +// with this package. Then it is only necessary to port this package +// to port all file handling. + +//////////////////////////////////////////////////////////////////////////////// +#include "portability_fixes.hpp" +#include +#include +#include +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // implement string comparison of paths - Unix is case-sensitive, Windows is case-insensitive + + bool path_compare(const std::string& l, const std::string& r); + + //////////////////////////////////////////////////////////////////////////////// + // classifying functions + + // test for whether there's something (i.e. folder or file) with this name + bool is_present(const std::string& thing); + // test for whether there's something present and its a folder + bool is_folder(const std::string& thing); + // test for whether there's something present and its a file + // a file can be a regular file, a symbolic link, a FIFO or a socket, but not a device + bool is_file(const std::string& thing); + + //////////////////////////////////////////////////////////////////////////////// + // file functions + + // tests whether there's a file of this name + bool file_exists(const std::string& filespec); + // tests whether the file is readable - i.e. exists and has read mode set + bool file_readable(const std::string& filespec); + // tests whether file is writable - either it exists and is writable or doesn't exist but is in a writable folder + bool file_writable(const std::string& filespec); + // the size of the file in bytes - 0 if doesn't exist + size_t file_size(const std::string& filespec); + // delete the file - returns true if the delete succeeded + bool file_delete(const std::string& filespec); + // rename the file - returns true if the rename succeeded + bool file_rename (const std::string& old_filespec, const std::string& new_filespec); + // make an exact copy of the file - returns true if it succeeded + bool file_copy (const std::string& old_filespec, const std::string& new_filespec); + // move the file - tries to rename, if that fails, tries to copy - returns true if either of these succeeded + bool file_move (const std::string& old_filespec, const std::string& new_filespec); + + // get the file's time stamps as a time_t - see the stlplus time.hpp package + + // time the file was originally created + time_t file_created(const std::string& filespec); + // time the file was last modified + time_t file_modified(const std::string& filespec); + // time the file was accessed + time_t file_accessed(const std::string& filespec); + + // platform-specific string handling to combine a directory and filename into a path + + // combine a folder with a filename (basename.extension) + std::string create_filespec(const std::string& folder, const std::string& filename); + // combine a folder, a basename and an extension - extension does not need the . + std::string create_filespec(const std::string& folder, const std::string& basename, const std::string& extension); + // combine a basename and an extension - extension does not need the . + std::string create_filename(const std::string& basename, const std::string& extension); + + //////////////////////////////////////////////////////////////////////////////// + // folder functions + + // craete a folder - returns true if successful + bool folder_create(const std::string& folder); + // tests for whether the folder exists, i.e. there is something of that name and its a folder + bool folder_exists(const std::string& folder); + // test whether the folder contents are readable + bool folder_readable(const std::string& folder); + // tests whether the folder can be written to - for example to create a new file + bool folder_writable(const std::string& folder); + // delete the folder, optionally deleting the contents first - only succeeds if everything could be deleted + bool folder_delete(const std::string& folder, bool recurse = false); + // rename the folder - this probably only works within a disk/partition + bool folder_rename (const std::string& old_directory, const std::string& new_directory); + // test whether the folder is empty (of files) + bool folder_empty(const std::string& folder); + + // set the current folder + bool folder_set_current(const std::string& folder); + + // platform-specific string handling to retrieve some special locations + // these do not check whether the folder exists, they just process strings + + // get the current folder + std::string folder_current(void); + // get the current folder as a full path + std::string folder_current_full(void); + // get the home folder - $HOME or %HOMEDRIVE%%HOMEPATH% + std::string folder_home(void); + // go down a level in the folder hierarchy + std::string folder_down(const std::string& folder, const std::string& subfolder); + // go up a level in the folder hierarchy + std::string folder_up(const std::string& folder, unsigned levels = 1); + + // get folder contents + + // the set of all subdirectories + std::vector folder_subdirectories(const std::string& folder); + // the set of all files + std::vector folder_files(const std::string& folder); + // the set of all folders and files + std::vector folder_all(const std::string& folder); + // the set of all folder contents matching a wildcard string + // if folders is true, include folders; if files is true, include files + std::vector folder_wildcard(const std::string& folder, + const std::string& wildcard, + bool folders = true, + bool files = true); + + //////////////////////////////////////////////////////////////////////////////// + // path functions + + // string manipulations of paths + + // test whether a string represents a full path or a relative one + bool is_full_path(const std::string& path); + bool is_relative_path(const std::string& path); + + // convert to a full path relative to the root path + std::string folder_to_path(const std::string& root, const std::string& folder); + std::string filespec_to_path(const std::string& root, const std::string& filespec); + + // convert to a full path relative to the current working directory + std::string folder_to_path(const std::string& folder); + std::string filespec_to_path(const std::string& filespec); + + // convert to a relative path relative to the root path + std::string folder_to_relative_path(const std::string& root, const std::string& folder); + std::string filespec_to_relative_path(const std::string& root, const std::string& filespec); + + // convert to a relative path relative to the current working directory + std::string folder_to_relative_path(const std::string& folder); + std::string filespec_to_relative_path(const std::string& filespec); + + // append a folder separator to the path to make it absolutely clear that it is a folder + std::string folder_append_separator(const std::string& folder); + + //////////////////////////////////////////////////////////////////////////////// + // access functions split a filespec into its elements + + // get the basename - that is, the name of the file without folder or extension parts + std::string basename_part(const std::string& filespec); + // get the filename - that is, the name of the file without folder part but with extension + std::string filename_part(const std::string& filespec); + // get the extension - that is the part of the filename after the . (and excluding the .) + std::string extension_part(const std::string& filespec); + // get the folder part - that is the filespec with the filename removed + std::string folder_part(const std::string& filespec); + + // split a path into a vector of elements - i.e. split at the folder separator + std::vector folder_elements(const std::string& folder); + std::vector filespec_elements(const std::string& filespec); + + //////////////////////////////////////////////////////////////////////////////// + // Path lookup functions + +#ifdef MSWINDOWS +#define PATH_SPLITTER ";" +#else +#define PATH_SPLITTER ":" +#endif + + // The lookup normally carried out by the shell to find a command in a + // directory in the PATH. Give this function the name of a command and it + // will return the full path. It returns an empty string on failure. + std::string path_lookup (const std::string& command); + + // Generalised form of the above, takes a second argument + // - the list to search. This can be used to do other path lookups, + // such as LD_LIBRARY_PATH. The third argument specifies the splitter - + // the default value of PATH_SPLITTER is appropriate for environment variables. + std::string lookup (const std::string& file, const std::string& path, const std::string& splitter = PATH_SPLITTER); + + // utility function for finding the folder that contains the current executable + // the argument is the argv[0] parameter passed to main + std::string install_path(const std::string& argv0); + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#endif diff --git a/src/stlplus/portability/inf.cpp b/src/stlplus/portability/inf.cpp index d70ab76..19e87ba 100644 --- a/src/stlplus/portability/inf.cpp +++ b/src/stlplus/portability/inf.cpp @@ -1,1482 +1,1482 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// The integer is represented as a sequence of bytes. They are stored such that -// element 0 is the lsB, which makes sense when seen as an integer offset but -// is counter-intuitive when you think that a string is usually read from left -// to right, 0 to size-1, in which case the lsB is on the *left*. - -// This solution is compatible with 32-bit and 64-bit machines with either -// little-endian or big-endian representations of integers. - -// Problem: I'm using std::string, which is an array of char. However, char is -// not well-defined - it could be signed or unsigned. - -// In fact, there's no requirement for a char to even be one byte - it can be -// any size of one byte or more. However, it's just impossible to make any -// progress with that naffness (thanks to the C non-standardisation committee) -// and the practice is that char on every platform/compiler I've ever come -// across is that char = byte. - -// The algorithms here use unsigned char to represent bit-patterns so I have to -// be careful to type-cast from char to unsigned char a lot. I use a typedef to -// make life easier. - -//////////////////////////////////////////////////////////////////////////////// -#include "inf.hpp" -#include -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // choose a sensible C type for a byte - - typedef unsigned char byte; - - //////////////////////////////////////////////////////////////////////////////// - // local functions - - // removes leading bytes that don't contribute to the value to create the minimum string representation - static void reduce_string(std::string& data) - { - while(data.size() > 1 && - ((byte(data[data.size()-1]) == byte(0) && byte(data[data.size()-2]) < byte(128)) || - (byte(data[data.size()-1]) == byte(255) && byte(data[data.size()-2]) >= byte(128)))) - { - data.erase(data.end()-1); - } - } - - // generic implementations of type conversions from integer type to internal representation - // data: integer value for conversion - // result: internal representation - - template - static void convert_from_signed(const T& data, std::string& result) - { - result.erase(); - bool lsb_first = little_endian(); - byte* address = (byte*)&data; - for (size_t i = 0; i < sizeof(T); i++) - { - size_t offset = (lsb_first ? i : (sizeof(T) - i - 1)); - result.append(1,address[offset]); - } - reduce_string(result); - } - - template - static void convert_from_unsigned(const T& data, std::string& result) - { - result.erase(); - bool lsb_first = little_endian(); - byte* address = (byte*)&data; - for (size_t i = 0; i < sizeof(T); i++) - { - size_t offset = (lsb_first ? i : (sizeof(T) - i - 1)); - result.append(1,address[offset]); - } - // inf is signed - so there is a possible extra sign bit to add - result.append(1,std::string::value_type(0)); - reduce_string(result); - } - - // generic implementations of type conversions from internal representation to an integer type - // data : string representation of integer - // result: integer result of conversion - // return: flag indicating success - false = overflow - - template - bool convert_to_signed(const std::string& data, T& result) - { - bool lsb_first = little_endian(); - byte* address = (byte*)&result; - for (size_t i = 0; i < sizeof(T); i++) - { - size_t offset = lsb_first ? i : (sizeof(T) - i - 1); - if (i < data.size()) - address[offset] = byte(data[i]); - else if (data.empty() || (byte(data[data.size()-1]) < byte(128))) - address[offset] = byte(0); - else - address[offset] = byte(255); - } - return data.size() <= sizeof(T); - } - - template - bool convert_to_unsigned(const std::string& data, T& result) - { - bool lsb_first = little_endian(); - byte* address = (byte*)&result; - for (size_t i = 0; i < sizeof(T); i++) - { - size_t offset = lsb_first ? i : (sizeof(T) - i - 1); - if (i < data.size()) - address[offset] = byte(data[i]); - else - address[offset] = byte(0); - } - return data.size() <= sizeof(T); - } - - //////////////////////////////////////////////////////////////////////////////// - // Conversions to string - - static char to_char [] = "0123456789abcdefghijklmnopqrstuvwxyz"; - static int from_char [] = - { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, - -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, - -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 - }; - - static void convert_to_string(const stlplus::inf& data, std::string& result, unsigned radix = 10) - throw(std::invalid_argument) - { - // only support the C-style radixes plus 0b for binary - if (radix != 2 && radix != 8 && radix != 10 && radix != 16) - throw std::invalid_argument("invalid radix value"); - inf local_i = data; - // untangle all the options - bool binary = radix == 2; - bool octal = radix == 8; - bool hex = radix == 16; - // the C representations for binary, octal and hex use 2's-complement representation - // all other represenations use sign-magnitude - if (hex || octal || binary) - { - // bit-pattern representation - // this is the binary representation optionally shown in octal or hex - // first generate the binary by masking the bits - for (unsigned j = local_i.bits(); j--; ) - result += (local_i.bit(j) ? '1' : '0'); - // the result is now the full width of the type - e.g. int will give a 32-bit result - // now interpret this as either binary, octal or hex and add the prefix - if (binary) - { - // trim down to the smallest string that preserves the value - while (true) - { - // do not trim to less than 1 bit (sign only) - if (result.size() <= 1) break; - // only trim if it doesn't change the sign and therefore the value - if (result[0] != result[1]) break; - result.erase(0,1); - } - // add the prefix - result.insert((std::string::size_type)0, "0b"); - } - else if (octal) - { - // the result is currently binary - // trim down to the smallest string that preserves the value - while (true) - { - // do not trim to less than 2 bits (sign plus 1-bit magnitude) - if (result.size() <= 2) break; - // only trim if it doesn't change the sign and therefore the value - if (result[0] != result[1]) break; - result.erase(0,1); - } - // also ensure that the binary is a multiple of 3 bits to make the conversion to octal easier - while (result.size() % 3 != 0) - result.insert((std::string::size_type)0, 1, result[0]); - // now convert to octal - std::string octal_result; - for (unsigned i = 0; i < result.size()/3; i++) - { - // yuck - ugly or what? - if (result[i*3] == '0') - { - if (result[i*3+1] == '0') - { - if (result[i*3+2] == '0') - octal_result += '0'; - else - octal_result += '1'; - } - else - { - if (result[i*3+2] == '0') - octal_result += '2'; - else - octal_result += '3'; - } - } - else - { - if (result[i*3+1] == '0') - { - if (result[i*3+2] == '0') - octal_result += '4'; - else - octal_result += '5'; - } - else - { - if (result[i*3+2] == '0') - octal_result += '6'; - else - octal_result += '7'; - } - } - } - result = octal_result; - // add the prefix - result.insert((std::string::size_type)0, "0"); - } - else - { - // similar to octal - while (true) - { - // do not trim to less than 2 bits (sign plus 1-bit magnitude) - if (result.size() <= 2) break; - // only trim if it doesn't change the sign and therefore the value - if (result[0] != result[1]) break; - result.erase(0,1); - } - // pad to a multiple of 4 characters - while (result.size() % 4 != 0) - result.insert((std::string::size_type)0, 1, result[0]); - // now convert to hex - std::string hex_result; - for (unsigned i = 0; i < result.size()/4; i++) - { - // yuck - ugly or what? - if (result[i*4] == '0') - { - if (result[i*4+1] == '0') - { - if (result[i*4+2] == '0') - { - if (result[i*4+3] == '0') - hex_result += '0'; - else - hex_result += '1'; - } - else - { - if (result[i*4+3] == '0') - hex_result += '2'; - else - hex_result += '3'; - } - } - else - { - if (result[i*4+2] == '0') - { - if (result[i*4+3] == '0') - hex_result += '4'; - else - hex_result += '5'; - } - else - { - if (result[i*4+3] == '0') - hex_result += '6'; - else - hex_result += '7'; - } - } - } - else - { - if (result[i*4+1] == '0') - { - if (result[i*4+2] == '0') - { - if (result[i*4+3] == '0') - hex_result += '8'; - else - hex_result += '9'; - } - else - { - if (result[i*4+3] == '0') - hex_result += 'a'; - else - hex_result += 'b'; - } - } - else - { - if (result[i*4+2] == '0') - { - if (result[i*4+3] == '0') - hex_result += 'c'; - else - hex_result += 'd'; - } - else - { - if (result[i*4+3] == '0') - hex_result += 'e'; - else - hex_result += 'f'; - } - } - } - } - result = hex_result; - // add the prefix - result.insert((std::string::size_type)0, "0x"); - } - } - else - { - // convert to sign-magnitude - // the representation is: - // [sign]magnitude - bool negative = local_i.negative(); - local_i.abs(); - // create a representation of the magnitude by successive division - inf inf_radix(radix); - do - { - std::pair divided = local_i.divide(inf_radix); - unsigned remainder = divided.second.to_unsigned(); - char digit = to_char[remainder]; - result.insert((std::string::size_type)0, 1, digit); - local_i = divided.first; - } - while(!local_i.zero()); - // add the prefixes - // add a sign only for negative values - if (negative) - result.insert((std::string::size_type)0, 1, '-'); - } - } - - //////////////////////////////////////////////////////////////////////////////// - // Conversions FROM string - - void convert_from_string(const std::string& str, inf& result, unsigned radix = 10) throw(std::invalid_argument) - { - result = 0; - // only support the C-style radixes plus 0b for binary - // a radix of 0 means deduce the radix from the input - assume 10 - if (radix != 0 && radix != 2 && radix != 8 && radix != 10 && radix != 16) - throw std::invalid_argument("invalid radix value"); - unsigned i = 0; - // the radix passed as a parameter is just the default - it can be - // overridden by the C prefix - // Note: a leading zero is the C-style prefix for octal - I only make this - // override the default when the default radix is not specified - // first check for a C-style prefix - bool c_style = false; - if (i < str.size() && str[i] == '0') - { - // binary or hex - if (i+1 < str.size() && tolower(str[i+1]) == 'x') - { - c_style = true; - radix = 16; - i += 2; - } - else if (i+1 < str.size() && tolower(str[i+1]) == 'b') - { - c_style = true; - radix = 2; - i += 2; - } - else if (radix == 0) - { - c_style = true; - radix = 8; - i += 1; - } - } - if (radix == 0) - radix = 10; - if (c_style) - { - // the C style formats are bit patterns not integer values - these need - // to be sign-extended to get the right value - std::string binary; - if (radix == 2) - { - for (unsigned j = i; j < str.size(); j++) - { - switch(str[j]) - { - case '0': - binary += '0'; - break; - case '1': - binary += '1'; - break; - default: - throw std::invalid_argument("invalid binary character in string " + str); - } - } - } - else if (radix == 8) - { - for (unsigned j = i; j < str.size(); j++) - { - switch(str[j]) - { - case '0': - binary += "000"; - break; - case '1': - binary += "001"; - break; - case '2': - binary += "010"; - break; - case '3': - binary += "011"; - break; - case '4': - binary += "100"; - break; - case '5': - binary += "101"; - break; - case '6': - binary += "110"; - break; - case '7': - binary += "111"; - break; - default: - throw std::invalid_argument("invalid octal character in string " + str); - } - } - } - else - { - for (unsigned j = i; j < str.size(); j++) - { - switch(tolower(str[j])) - { - case '0': - binary += "0000"; - break; - case '1': - binary += "0001"; - break; - case '2': - binary += "0010"; - break; - case '3': - binary += "0011"; - break; - case '4': - binary += "0100"; - break; - case '5': - binary += "0101"; - break; - case '6': - binary += "0110"; - break; - case '7': - binary += "0111"; - break; - case '8': - binary += "1000"; - break; - case '9': - binary += "1001"; - break; - case 'a': - binary += "1010"; - break; - case 'b': - binary += "1011"; - break; - case 'c': - binary += "1100"; - break; - case 'd': - binary += "1101"; - break; - case 'e': - binary += "1110"; - break; - case 'f': - binary += "1111"; - break; - default: - throw std::invalid_argument("invalid hex character in string " + str); - } - } - } - // now convert the value - result.resize(binary.size()); - for (unsigned j = 0; j < binary.size(); j++) - result.preset(binary.size() - j - 1, binary[j] == '1'); - } - else - { - // sign-magnitude representation - // now scan for a sign and find whether this is a negative number - bool negative = false; - if (i < str.size()) - { - switch (str[i]) - { - case '-': - negative = true; - i++; - break; - case '+': - i++; - break; - } - } - for (; i < str.size(); i++) - { - result *= inf(radix); - unsigned char ascii = (unsigned char)str[i]; - int ch = from_char[ascii] ; - if (ch == -1) - throw std::invalid_argument("invalid decimal character in string " + str); - result += inf(ch); - } - if (negative) - result.negate(); - } - } - - //////////////////////////////////////////////////////////////////////////////// - // constructors - mostly implemented in terms of the assignment operators - - inf::inf(void) - { - // void constructor initialises to zero - represented as a single-byte value containing zero - m_data.append(1,std::string::value_type(0)); - } - - inf::inf(short r) - { - operator=(r); - } - - inf::inf(unsigned short r) - { - operator=(r); - } - - inf::inf(int r) - { - operator=(r); - } - - inf::inf(unsigned r) - { - operator=(r); - } - - inf::inf(long r) - { - operator=(r); - } - - inf::inf(unsigned long r) - { - operator=(r); - } - - inf::inf (const std::string& r) throw(std::invalid_argument) - { - operator=(r); - } - - inf::inf(const inf& r) - { -#ifdef __BORLANDC__ - // work round bug in Borland compiler - copy constructor fails if string - // contains null characters, so do my own copy - for (unsigned i = 0; i < r.m_data.size(); i++) - m_data += r.m_data[i]; -#else - m_data = r.m_data; -#endif - } - - //////////////////////////////////////////////////////////////////////////////// - - inf::~inf(void) - { - } - - //////////////////////////////////////////////////////////////////////////////// - // assignments convert from iteger types to internal representation - - inf& inf::operator = (short r) - { - convert_from_signed(r, m_data); - return *this; - } - - inf& inf::operator = (unsigned short r) - { - convert_from_unsigned(r, m_data); - return *this; - } - - inf& inf::operator = (int r) - { - convert_from_signed(r, m_data); - return *this; - } - - inf& inf::operator = (unsigned r) - { - convert_from_unsigned(r, m_data); - return *this; - } - - inf& inf::operator = (long r) - { - convert_from_signed(r, m_data); - return *this; - } - - inf& inf::operator = (unsigned long r) - { - convert_from_unsigned(r, m_data); - return *this; - } - - inf& inf::operator = (const std::string& r) throw(std::invalid_argument) - { - convert_from_string(r, *this); - return *this; - } - - inf& inf::operator = (const inf& r) - { - m_data = r.m_data; - return *this; - } - - //////////////////////////////////////////////////////////////////////////////// - - short inf::to_short(bool truncate) const throw(std::overflow_error) - { - short result = 0; - if (!convert_to_signed(m_data, result)) - if (!truncate) - throw std::overflow_error("stlplus::inf::to_short"); - return result; - } - - unsigned short inf::to_unsigned_short(bool truncate) const throw(std::overflow_error) - { - unsigned short result = 0; - if (!convert_to_unsigned(m_data, result)) - if (!truncate) - throw std::overflow_error("stlplus::inf::to_unsigned_short"); - return result; - } - - int inf::to_int(bool truncate) const throw(std::overflow_error) - { - int result = 0; - if (!convert_to_signed(m_data, result)) - if (!truncate) - throw std::overflow_error("stlplus::inf::to_int"); - return result; - } - - unsigned inf::to_unsigned(bool truncate) const throw(std::overflow_error) - { - unsigned result = 0; - if (!convert_to_unsigned(m_data, result)) - if (!truncate) - throw std::overflow_error("stlplus::inf::to_unsigned"); - return result; - } - - long inf::to_long(bool truncate) const throw(std::overflow_error) - { - long result = 0; - if (!convert_to_signed(m_data, result)) - if (!truncate) - throw std::overflow_error("stlplus::inf::to_long"); - return result; - } - - unsigned long inf::to_unsigned_long(bool truncate) const throw(std::overflow_error) - { - unsigned long result = 0; - if (!convert_to_unsigned(m_data, result)) - if (!truncate) - throw std::overflow_error("stlplus::inf::to_unsigned_long"); - return result; - } - - //////////////////////////////////////////////////////////////////////////////// - // resize the inf regardless of the data - - void inf::resize(unsigned bits) - { - if (bits == 0) bits = 1; - unsigned bytes = (bits+7)/8; - byte extend = negative() ? byte(255) : byte (0); - while(bytes > m_data.size()) - m_data.append(1,extend); - } - - // reduce the bit count to the minimum needed to preserve the value - - void inf::reduce(void) - { - reduce_string(m_data); - } - - //////////////////////////////////////////////////////////////////////////////// - // the number of significant bits in the number - - unsigned inf::bits (void) const - { - // The number of significant bits in the integer value - this is the number - // of indexable bits less any redundant sign bits at the msb - // This does not assume that the inf has been reduced to its minimum form - unsigned result = indexable_bits(); - bool sign = bit(result-1); - while (result > 1 && (sign == bit(result-2))) - result--; - return result; - } - - unsigned inf::size(void) const - { - return bits(); - } - - unsigned inf::indexable_bits (void) const - { - return 8 * unsigned(m_data.size()); - } - - //////////////////////////////////////////////////////////////////////////////// - // bitwise operations - - bool inf::bit (unsigned index) const throw(std::out_of_range) - { - if (index >= indexable_bits()) - throw std::out_of_range(std::string("stlplus::inf::bit")); - // first split the offset into byte offset and bit offset - unsigned byte_offset = index/8; - unsigned bit_offset = index%8; - return (byte(m_data[byte_offset]) & (byte(1) << bit_offset)) != 0; - } - - bool inf::operator [] (unsigned index) const throw(std::out_of_range) - { - return bit(index); - } - - void inf::set (unsigned index) throw(std::out_of_range) - { - if (index >= indexable_bits()) - throw std::out_of_range(std::string("stlplus::inf::set")); - // first split the offset into byte offset and bit offset - unsigned byte_offset = index/8; - unsigned bit_offset = index%8; - m_data[byte_offset] |= (byte(1) << bit_offset); - } - - void inf::clear (unsigned index) throw(std::out_of_range) - { - if (index >= indexable_bits()) - throw std::out_of_range(std::string("stlplus::inf::clear")); - // first split the offset into byte offset and bit offset - unsigned byte_offset = index/8; - unsigned bit_offset = index%8; - m_data[byte_offset] &= (~(byte(1) << bit_offset)); - } - - void inf::preset (unsigned index, bool value) throw(std::out_of_range) - { - if (value) - set(index); - else - clear(index); - } - - inf inf::slice(unsigned low, unsigned high) const throw(std::out_of_range) - { - if (low >= indexable_bits()) - throw std::out_of_range(std::string("stlplus::inf::slice: low index")); - if (high >= indexable_bits()) - throw std::out_of_range(std::string("stlplus::inf::slice: high index")); - inf result; - if (high >= low) - { - // create a result the right size and filled with sign bits - std::string::size_type result_size = (high-low+1+7)/8; - result.m_data.erase(); - byte extend = bit(high) ? byte(255) : byte (0); - while (result.m_data.size() < result_size) - result.m_data.append(1,extend); - // now set the relevant bits - for (unsigned i = low; i <= high; i++) - result.preset(i-low, bit(i)); - } - return result; - } - - //////////////////////////////////////////////////////////////////////////////// - // testing operations - - bool inf::negative (void) const - { - return bit(indexable_bits()-1); - } - - bool inf::natural (void) const - { - return !negative(); - } - - bool inf::positive (void) const - { - return natural() && !zero(); - } - - bool inf::zero (void) const - { - for (std::string::size_type i = 0; i < m_data.size(); i++) - if (m_data[i] != 0) - return false; - return true; - } - - bool inf::non_zero (void) const - { - return !zero(); - } - - bool inf::operator ! (void) const - { - return zero(); - } - - //////////////////////////////////////////////////////////////////////////////// - // comparison operators - - bool inf::operator == (const inf& r) const - { - // Two infs are equal if they are numerically equal, even if they are - // different sizes (i.e. they could be non-reduced values). - // This makes life a little more complicated than if I could assume that values were reduced. - byte l_extend = negative() ? byte(255) : byte (0); - byte r_extend = r.negative() ? byte(255) : byte (0); - std::string::size_type bytes = maximum(m_data.size(),r.m_data.size()); - for (std::string::size_type i = bytes; i--; ) - { - byte l_byte = (i < m_data.size() ? byte(m_data[i]) : l_extend); - byte r_byte = (i < r.m_data.size() ? byte(r.m_data[i]) : r_extend); - if (l_byte != r_byte) - return false; - } - return true; - } - - bool inf::operator != (const inf& r) const - { - return !operator==(r); - } - - bool inf::operator < (const inf& r) const - { - // This could be implemented in terms of subtraction. However, it can be - // simplified since there is no need to calculate the accurate difference, - // just the direction of the difference. I compare from msB down and as - // soon as a byte difference is found, that defines the ordering. The - // problem is that in 2's-complement, all negative values are greater than - // all natural values if you just do a straight unsigned comparison. I - // handle this by doing a preliminary test for different signs. - - // For example, a 3-bit signed type has the coding: - // 000 = 0 - // ... - // 011 = 3 - // 100 = -4 - // ... - // 111 = -1 - - // So, for natural values, the ordering of the integer values is the - // ordering of the bit patterns. Similarly, for negative values, the - // ordering of the integer values is the ordering of the bit patterns - // However, the bit patterns for the negative values are *greater than* - // the natural values. This is a side-effect of the naffness of - // 2's-complement representation - - // first handle the case of comparing two values with different signs - bool l_sign = negative(); - bool r_sign = r.negative(); - if (l_sign != r_sign) - { - // one argument must be negative and the other natural - // the left is less if it is the negative one - return l_sign; - } - // the arguments are the same sign - // so the ordering is a simple unsigned byte-by-byte comparison - // However, this is complicated by the possibility that the values could be different lengths - byte l_extend = l_sign ? byte(255) : byte (0); - byte r_extend = r_sign ? byte(255) : byte (0); - std::string::size_type bytes = maximum(m_data.size(),r.m_data.size()); - for (std::string::size_type i = bytes; i--; ) - { - byte l_byte = (i < m_data.size() ? byte(m_data[i]) : l_extend); - byte r_byte = (i < r.m_data.size() ? byte(r.m_data[i]) : r_extend); - if (l_byte != r_byte) - return l_byte < r_byte; - } - // if I get here, the two are equal, so that is not less-than - return false; - } - - bool inf::operator <= (const inf& r) const - { - return !(r < *this); - } - - bool inf::operator > (const inf& r) const - { - return r < *this; - } - - bool inf::operator >= (const inf& r) const - { - return !(*this < r); - } - - //////////////////////////////////////////////////////////////////////////////// - // logical operators - - inf& inf::invert (void) - { - for (std::string::size_type i = 0; i < m_data.size(); i++) - m_data[i] = ~m_data[i]; - return *this; - } - - inf inf::operator ~ (void) const - { - inf result(*this); - result.invert(); - return result; - } - - inf& inf::operator &= (const inf& r) - { - // bitwise AND is extended to the length of the largest argument - byte l_extend = negative() ? byte(255) : byte (0); - byte r_extend = r.negative() ? byte(255) : byte (0); - std::string::size_type bytes = maximum(m_data.size(),r.m_data.size()); - for (std::string::size_type i = 0; i < bytes; i++) - { - byte l_byte = (i < m_data.size() ? byte(m_data[i]) : l_extend); - byte r_byte = (i < r.m_data.size() ? byte(r.m_data[i]) : r_extend); - byte result = l_byte & r_byte; - if (i < m_data.size()) - m_data[i] = result; - else - m_data.append(1,result); - } - // now reduce the result - reduce(); - return *this; - } - - inf inf::operator & (const inf& r) const - { - inf result(*this); - result &= r; - return result; - } - - inf& inf::operator |= (const inf& r) - { - // bitwise OR is extended to the length of the largest argument - byte l_extend = negative() ? byte(255) : byte (0); - byte r_extend = r.negative() ? byte(255) : byte (0); - std::string::size_type bytes = maximum(m_data.size(),r.m_data.size()); - for (std::string::size_type i = 0; i < bytes; i++) - { - byte l_byte = (i < m_data.size() ? byte(m_data[i]) : l_extend); - byte r_byte = (i < r.m_data.size() ? byte(r.m_data[i]) : r_extend); - byte result = l_byte | r_byte; - if (i < m_data.size()) - m_data[i] = result; - else - m_data.append(1,result); - } - // now reduce the result - reduce(); - return *this; - } - - inf inf::operator | (const inf& r) const - { - inf result(*this); - result |= r; - return result; - } - - inf& inf::operator ^= (const inf& r) - { - // bitwise XOR is extended to the length of the largest argument - byte l_extend = negative() ? byte(255) : byte (0); - byte r_extend = r.negative() ? byte(255) : byte (0); - std::string::size_type bytes = maximum(m_data.size(),r.m_data.size()); - for (std::string::size_type i = 0; i < bytes; i++) - { - byte l_byte = (i < m_data.size() ? byte(m_data[i]) : l_extend); - byte r_byte = (i < r.m_data.size() ? byte(r.m_data[i]) : r_extend); - byte result = l_byte ^ r_byte; - if (i < m_data.size()) - m_data[i] = result; - else - m_data.append(1,result); - } - // now reduce the result - reduce(); - return *this; - } - - inf inf::operator ^ (const inf& r) const - { - inf result(*this); - result ^= r; - return result; - } - - //////////////////////////////////////////////////////////////////////////////// - // shift operators all preserve the value by increasing the word size - - inf& inf::operator <<= (unsigned shift) - { - // left shift is a shift towards the msb, with 0s being shifted in at the lsb - // split this into a byte shift followed by a bit shift - - // first expand the value to be big enough for the result - std::string::size_type new_size = (indexable_bits() + shift + 7) / 8; - byte extend = negative() ? byte(255) : byte (0); - while (m_data.size() < new_size) - m_data.append(1,extend); - // now do the byte shift - unsigned byte_shift = shift/8; - if (byte_shift > 0) - { - for (std::string::size_type b = new_size; b--; ) - m_data[b] = (b >= byte_shift) ? m_data[b-byte_shift] : byte(0); - } - // and finally the bit shift - unsigned bit_shift = shift%8; - if (bit_shift > 0) - { - for (std::string::size_type b = new_size; b--; ) - { - byte current = byte(m_data[b]); - byte previous = b > 0 ? m_data[b-1] : byte(0); - m_data[b] = (current << bit_shift) | (previous >> (8 - bit_shift)); - } - } - // now reduce the result - reduce(); - return *this; - } - - inf inf::operator << (unsigned shift) const - { - inf result(*this); - result <<= shift; - return result; - } - - inf& inf::operator >>= (unsigned shift) - { - // right shift is a shift towards the lsb, with sign bits being shifted in at the msb - // split this into a byte shift followed by a bit shift - - // a byte of sign bits - byte extend = negative() ? byte(255) : byte (0); - // do the byte shift - unsigned byte_shift = shift/8; - if (byte_shift > 0) - { - for (std::string::size_type b = 0; b < m_data.size(); b++) - m_data[b] = (b + byte_shift < m_data.size()) ? m_data[b+byte_shift] : extend; - } - // and finally the bit shift - unsigned bit_shift = shift%8; - if (bit_shift > 0) - { - for (std::string::size_type b = 0; b < m_data.size(); b++) - { - byte current = byte(m_data[b]); - byte next = ((b+1) < m_data.size()) ? m_data[b+1] : extend; - byte shifted = (current >> bit_shift) | (next << (8 - bit_shift)); - m_data[b] = shifted; - } - } - // now reduce the result - reduce(); - return *this; - } - - inf inf::operator >> (unsigned shift) const - { - inf result(*this); - result >>= shift; - return result; - } - - //////////////////////////////////////////////////////////////////////////////// - // negation operators - - inf& inf::negate (void) - { - // do 2's-complement negation - // equivalent to inversion plus one - invert(); - operator += (inf(1)); - return *this; - } - - inf inf::operator - (void) const - { - inf result(*this); - result.negate(); - return result; - } - - inf& inf::abs(void) - { - if (negative()) negate(); - return *this; - } - - inf abs(const inf& i) - { - inf result = i; - result.abs(); - return result; - } - - //////////////////////////////////////////////////////////////////////////////// - // addition operators - - inf& inf::operator += (const inf& r) - { - // do 2's-complement addition - // Note that the addition can give a result that is larger than either argument - byte carry = 0; - std::string::size_type max_size = maximum(m_data.size(),r.m_data.size()); - byte l_extend = negative() ? byte(255) : byte (0); - byte r_extend = r.negative() ? byte(255) : byte (0); - for (std::string::size_type i = 0; i < max_size; i++) - { - byte l_byte = (i < m_data.size() ? byte(m_data[i]) : l_extend); - byte r_byte = (i < r.m_data.size() ? byte(r.m_data[i]) : r_extend); - // calculate the addition in a type that is bigger than a byte in order to catch the carry-out - unsigned short result = ((unsigned short)(l_byte)) + ((unsigned short)(r_byte)) + carry; - // now truncate the result to get the lsB - if (i < m_data.size()) - m_data[i] = byte(result); - else - m_data.append(1,byte(result)); - // and capture the carry out by grabbing the second byte of the result - carry = byte(result >> 8); - } - // if the result overflowed or underflowed, add an extra byte to catch it - unsigned short result = ((unsigned short)(l_extend)) + ((unsigned short)(r_extend)) + carry; - if (byte(result) != (negative() ? byte(255) : byte(0))) - m_data.append(1,byte(result)); - // now reduce the result - reduce(); - return *this; - } - - inf inf::operator + (const inf& r) const - { - inf result(*this); - result += r; - return result; - } - - //////////////////////////////////////////////////////////////////////////////// - // subtraction operators - - inf& inf::operator -= (const inf& r) - { - // subtraction is defined in terms of negation and addition - inf negated = -r; - operator += (negated); - return *this; - } - - inf inf::operator - (const inf& r) const - { - inf result(*this); - result -= r; - return result; - } - - //////////////////////////////////////////////////////////////////////////////// - // multiplication operators - - inf& inf::operator *= (const inf& r) - { - // 2's complement multiplication - // one day I'll do a more efficient version than this based on the underlying representation - inf left(*this); - inf right = r; - // make the right value natural but preserve its sign for later - bool right_negative = right.negative(); - right.abs(); - // implemented as a series of conditional additions - operator = (0); - // left.resize(right.bits() + left.bits() - 1); - left <<= right.bits()-1; - for (unsigned i = right.bits(); i--; ) - { - if (right[i]) - operator += (left); - left >>= 1; - } - if (right_negative) - negate(); - // now reduce the result - reduce(); - return *this; - } - - inf inf::operator * (const inf& r) const - { - inf result(*this); - result *= r; - return result; - } - - //////////////////////////////////////////////////////////////////////////////// - // division and remainder operators - - std::pair inf::divide(const inf& right) const throw(divide_by_zero) - { - if (right.zero()) - throw divide_by_zero("stlplus::inf::divide"); - inf numerator(*this); - inf denominator = right; - // make the numerator natural but preserve the sign for later - bool numerator_negative = numerator.negative(); - numerator.abs(); - // same with the denominator - bool denominator_negative = denominator.negative(); - denominator.abs(); - // the quotient and remainder will form the result - // start with the quotiont zero and the remainder equal to the whole of the - // numerator, then do trial subtraction from this - inf quotient; - inf remainder = numerator; - // there's nothing more to do if the numerator is smaller than the denominator - // but otherwise do the division - if (numerator.bits() >= denominator.bits()) - { - // make the quotient big enough to take the result - quotient.resize(numerator.bits()); - // start with the numerator shifted to the far left - unsigned shift = numerator.bits() - denominator.bits(); - denominator <<= shift; - // do the division by repeated subtraction, - for (unsigned i = shift+1; i--; ) - { - if (remainder >= denominator) - { - remainder -= denominator; - quotient.set(i); - } - denominator >>= 1; - } - } - // now adjust the signs - // x/(-y) == (-x)/y == -(x/y) - if (numerator_negative != denominator_negative) - quotient.negate(); - quotient.reduce(); - // x%(-y) == x%y and (-x)%y == -(x%y) - if (numerator_negative) - remainder.negate(); - remainder.reduce(); - return std::pair(quotient,remainder); - } - - inf& inf::operator /= (const inf& r) throw(divide_by_zero) - { - std::pair result = divide(r); - operator=(result.first); - return *this; - } - - inf inf::operator / (const inf& r) const throw(divide_by_zero) - { - std::pair result = divide(r); - return result.first; - } - - inf& inf::operator %= (const inf& r) throw(divide_by_zero) - { - std::pair result = divide(r); - operator=(result.second); - return *this; - } - - inf inf::operator % (const inf& r) const throw(divide_by_zero) - { - std::pair result = divide(r); - return result.second; - } - - //////////////////////////////////////////////////////////////////////////////// - // prefix (void) and postfix (int) operators - - inf& inf::operator ++ (void) - { - operator += (inf(1)); - return *this; - } - - inf inf::operator ++ (int) - { - inf old(*this); - operator += (inf(1)); - return old; - } - - inf& inf::operator -- (void) - { - operator -= (inf(1)); - return *this; - } - - inf inf::operator -- (int) - { - inf old(*this); - operator -= (inf(1)); - return old; - } - - //////////////////////////////////////////////////////////////////////////////// - // string representation and I/O routines - - std::string inf::to_string(unsigned radix) const - throw(std::invalid_argument) - { - std::string result; - convert_to_string(*this, result, radix); - return result; - } - - inf& inf::from_string(const std::string& value, unsigned radix) - throw(std::invalid_argument) - { - convert_from_string(value, *this, radix); - return *this; - } - - std::ostream& operator << (std::ostream& str, const inf& i) - { - try - { - // get radix - unsigned radix = 10; - if (str.flags() & std::ios_base::oct) - radix = 8; - if (str.flags() & std::ios_base::hex) - radix = 16; - // the field width is handled by iostream, so I don't need to handle it as well - // generate the string representation then print it - str << i.to_string(radix); - } - catch(const std::invalid_argument) - { - str.setstate(std::ios_base::badbit); - } - return str; - } - - std::istream& operator >> (std::istream& str, inf& i) - { - try - { - // get radix - unsigned radix = 10; - if (str.flags() & std::ios_base::oct) - radix = 8; - if (str.flags() & std::ios_base::hex) - radix = 16; - // now get the string image of the value - std::string image; - str >> image; - // and convert to inf - i.from_string(image, radix); - } - catch(const std::invalid_argument) - { - str.setstate(std::ios_base::badbit); - } - return str; - } - - //////////////////////////////////////////////////////////////////////////////// - // diagnostic dump - // just convert to hex - - std::string inf::image_debug(void) const - { - // create this dump in the human-readable form, i.e. msB to the left - std::string result = "0x"; - for (std::string::size_type i = m_data.size(); i--; ) - { - byte current = m_data[i]; - byte msB = (current & byte(0xf0)) >> 4; - result += to_char[msB]; - byte lsB = (current & byte(0x0f)); - result += to_char[lsB]; - } - return result; - } - - const std::string& inf::get_bytes(void) const - { - return m_data; - } - - void inf::set_bytes(const std::string& data) - { - m_data = data; - } - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// The integer is represented as a sequence of bytes. They are stored such that +// element 0 is the lsB, which makes sense when seen as an integer offset but +// is counter-intuitive when you think that a string is usually read from left +// to right, 0 to size-1, in which case the lsB is on the *left*. + +// This solution is compatible with 32-bit and 64-bit machines with either +// little-endian or big-endian representations of integers. + +// Problem: I'm using std::string, which is an array of char. However, char is +// not well-defined - it could be signed or unsigned. + +// In fact, there's no requirement for a char to even be one byte - it can be +// any size of one byte or more. However, it's just impossible to make any +// progress with that naffness (thanks to the C non-standardisation committee) +// and the practice is that char on every platform/compiler I've ever come +// across is that char = byte. + +// The algorithms here use unsigned char to represent bit-patterns so I have to +// be careful to type-cast from char to unsigned char a lot. I use a typedef to +// make life easier. + +//////////////////////////////////////////////////////////////////////////////// +#include "inf.hpp" +#include +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // choose a sensible C type for a byte + + typedef unsigned char byte; + + //////////////////////////////////////////////////////////////////////////////// + // local functions + + // removes leading bytes that don't contribute to the value to create the minimum string representation + static void reduce_string(std::string& data) + { + while(data.size() > 1 && + ((byte(data[data.size()-1]) == byte(0) && byte(data[data.size()-2]) < byte(128)) || + (byte(data[data.size()-1]) == byte(255) && byte(data[data.size()-2]) >= byte(128)))) + { + data.erase(data.end()-1); + } + } + + // generic implementations of type conversions from integer type to internal representation + // data: integer value for conversion + // result: internal representation + + template + static void convert_from_signed(const T& data, std::string& result) + { + result.erase(); + bool lsb_first = little_endian(); + byte* address = (byte*)&data; + for (size_t i = 0; i < sizeof(T); i++) + { + size_t offset = (lsb_first ? i : (sizeof(T) - i - 1)); + result.append(1,address[offset]); + } + reduce_string(result); + } + + template + static void convert_from_unsigned(const T& data, std::string& result) + { + result.erase(); + bool lsb_first = little_endian(); + byte* address = (byte*)&data; + for (size_t i = 0; i < sizeof(T); i++) + { + size_t offset = (lsb_first ? i : (sizeof(T) - i - 1)); + result.append(1,address[offset]); + } + // inf is signed - so there is a possible extra sign bit to add + result.append(1,std::string::value_type(0)); + reduce_string(result); + } + + // generic implementations of type conversions from internal representation to an integer type + // data : string representation of integer + // result: integer result of conversion + // return: flag indicating success - false = overflow + + template + bool convert_to_signed(const std::string& data, T& result) + { + bool lsb_first = little_endian(); + byte* address = (byte*)&result; + for (size_t i = 0; i < sizeof(T); i++) + { + size_t offset = lsb_first ? i : (sizeof(T) - i - 1); + if (i < data.size()) + address[offset] = byte(data[i]); + else if (data.empty() || (byte(data[data.size()-1]) < byte(128))) + address[offset] = byte(0); + else + address[offset] = byte(255); + } + return data.size() <= sizeof(T); + } + + template + bool convert_to_unsigned(const std::string& data, T& result) + { + bool lsb_first = little_endian(); + byte* address = (byte*)&result; + for (size_t i = 0; i < sizeof(T); i++) + { + size_t offset = lsb_first ? i : (sizeof(T) - i - 1); + if (i < data.size()) + address[offset] = byte(data[i]); + else + address[offset] = byte(0); + } + return data.size() <= sizeof(T); + } + + //////////////////////////////////////////////////////////////////////////////// + // Conversions to string + + static char to_char [] = "0123456789abcdefghijklmnopqrstuvwxyz"; + static int from_char [] = + { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + + static void convert_to_string(const stlplus::inf& data, std::string& result, unsigned radix = 10) + throw(std::invalid_argument) + { + // only support the C-style radixes plus 0b for binary + if (radix != 2 && radix != 8 && radix != 10 && radix != 16) + throw std::invalid_argument("invalid radix value"); + inf local_i = data; + // untangle all the options + bool binary = radix == 2; + bool octal = radix == 8; + bool hex = radix == 16; + // the C representations for binary, octal and hex use 2's-complement representation + // all other represenations use sign-magnitude + if (hex || octal || binary) + { + // bit-pattern representation + // this is the binary representation optionally shown in octal or hex + // first generate the binary by masking the bits + for (unsigned j = local_i.bits(); j--; ) + result += (local_i.bit(j) ? '1' : '0'); + // the result is now the full width of the type - e.g. int will give a 32-bit result + // now interpret this as either binary, octal or hex and add the prefix + if (binary) + { + // trim down to the smallest string that preserves the value + while (true) + { + // do not trim to less than 1 bit (sign only) + if (result.size() <= 1) break; + // only trim if it doesn't change the sign and therefore the value + if (result[0] != result[1]) break; + result.erase(0,1); + } + // add the prefix + result.insert((std::string::size_type)0, "0b"); + } + else if (octal) + { + // the result is currently binary + // trim down to the smallest string that preserves the value + while (true) + { + // do not trim to less than 2 bits (sign plus 1-bit magnitude) + if (result.size() <= 2) break; + // only trim if it doesn't change the sign and therefore the value + if (result[0] != result[1]) break; + result.erase(0,1); + } + // also ensure that the binary is a multiple of 3 bits to make the conversion to octal easier + while (result.size() % 3 != 0) + result.insert((std::string::size_type)0, 1, result[0]); + // now convert to octal + std::string octal_result; + for (unsigned i = 0; i < result.size()/3; i++) + { + // yuck - ugly or what? + if (result[i*3] == '0') + { + if (result[i*3+1] == '0') + { + if (result[i*3+2] == '0') + octal_result += '0'; + else + octal_result += '1'; + } + else + { + if (result[i*3+2] == '0') + octal_result += '2'; + else + octal_result += '3'; + } + } + else + { + if (result[i*3+1] == '0') + { + if (result[i*3+2] == '0') + octal_result += '4'; + else + octal_result += '5'; + } + else + { + if (result[i*3+2] == '0') + octal_result += '6'; + else + octal_result += '7'; + } + } + } + result = octal_result; + // add the prefix + result.insert((std::string::size_type)0, "0"); + } + else + { + // similar to octal + while (true) + { + // do not trim to less than 2 bits (sign plus 1-bit magnitude) + if (result.size() <= 2) break; + // only trim if it doesn't change the sign and therefore the value + if (result[0] != result[1]) break; + result.erase(0,1); + } + // pad to a multiple of 4 characters + while (result.size() % 4 != 0) + result.insert((std::string::size_type)0, 1, result[0]); + // now convert to hex + std::string hex_result; + for (unsigned i = 0; i < result.size()/4; i++) + { + // yuck - ugly or what? + if (result[i*4] == '0') + { + if (result[i*4+1] == '0') + { + if (result[i*4+2] == '0') + { + if (result[i*4+3] == '0') + hex_result += '0'; + else + hex_result += '1'; + } + else + { + if (result[i*4+3] == '0') + hex_result += '2'; + else + hex_result += '3'; + } + } + else + { + if (result[i*4+2] == '0') + { + if (result[i*4+3] == '0') + hex_result += '4'; + else + hex_result += '5'; + } + else + { + if (result[i*4+3] == '0') + hex_result += '6'; + else + hex_result += '7'; + } + } + } + else + { + if (result[i*4+1] == '0') + { + if (result[i*4+2] == '0') + { + if (result[i*4+3] == '0') + hex_result += '8'; + else + hex_result += '9'; + } + else + { + if (result[i*4+3] == '0') + hex_result += 'a'; + else + hex_result += 'b'; + } + } + else + { + if (result[i*4+2] == '0') + { + if (result[i*4+3] == '0') + hex_result += 'c'; + else + hex_result += 'd'; + } + else + { + if (result[i*4+3] == '0') + hex_result += 'e'; + else + hex_result += 'f'; + } + } + } + } + result = hex_result; + // add the prefix + result.insert((std::string::size_type)0, "0x"); + } + } + else + { + // convert to sign-magnitude + // the representation is: + // [sign]magnitude + bool negative = local_i.negative(); + local_i.abs(); + // create a representation of the magnitude by successive division + inf inf_radix(radix); + do + { + std::pair divided = local_i.divide(inf_radix); + unsigned remainder = divided.second.to_unsigned(); + char digit = to_char[remainder]; + result.insert((std::string::size_type)0, 1, digit); + local_i = divided.first; + } + while(!local_i.zero()); + // add the prefixes + // add a sign only for negative values + if (negative) + result.insert((std::string::size_type)0, 1, '-'); + } + } + + //////////////////////////////////////////////////////////////////////////////// + // Conversions FROM string + + void convert_from_string(const std::string& str, inf& result, unsigned radix = 10) throw(std::invalid_argument) + { + result = 0; + // only support the C-style radixes plus 0b for binary + // a radix of 0 means deduce the radix from the input - assume 10 + if (radix != 0 && radix != 2 && radix != 8 && radix != 10 && radix != 16) + throw std::invalid_argument("invalid radix value"); + unsigned i = 0; + // the radix passed as a parameter is just the default - it can be + // overridden by the C prefix + // Note: a leading zero is the C-style prefix for octal - I only make this + // override the default when the default radix is not specified + // first check for a C-style prefix + bool c_style = false; + if (i < str.size() && str[i] == '0') + { + // binary or hex + if (i+1 < str.size() && tolower(str[i+1]) == 'x') + { + c_style = true; + radix = 16; + i += 2; + } + else if (i+1 < str.size() && tolower(str[i+1]) == 'b') + { + c_style = true; + radix = 2; + i += 2; + } + else if (radix == 0) + { + c_style = true; + radix = 8; + i += 1; + } + } + if (radix == 0) + radix = 10; + if (c_style) + { + // the C style formats are bit patterns not integer values - these need + // to be sign-extended to get the right value + std::string binary; + if (radix == 2) + { + for (unsigned j = i; j < str.size(); j++) + { + switch(str[j]) + { + case '0': + binary += '0'; + break; + case '1': + binary += '1'; + break; + default: + throw std::invalid_argument("invalid binary character in string " + str); + } + } + } + else if (radix == 8) + { + for (unsigned j = i; j < str.size(); j++) + { + switch(str[j]) + { + case '0': + binary += "000"; + break; + case '1': + binary += "001"; + break; + case '2': + binary += "010"; + break; + case '3': + binary += "011"; + break; + case '4': + binary += "100"; + break; + case '5': + binary += "101"; + break; + case '6': + binary += "110"; + break; + case '7': + binary += "111"; + break; + default: + throw std::invalid_argument("invalid octal character in string " + str); + } + } + } + else + { + for (unsigned j = i; j < str.size(); j++) + { + switch(tolower(str[j])) + { + case '0': + binary += "0000"; + break; + case '1': + binary += "0001"; + break; + case '2': + binary += "0010"; + break; + case '3': + binary += "0011"; + break; + case '4': + binary += "0100"; + break; + case '5': + binary += "0101"; + break; + case '6': + binary += "0110"; + break; + case '7': + binary += "0111"; + break; + case '8': + binary += "1000"; + break; + case '9': + binary += "1001"; + break; + case 'a': + binary += "1010"; + break; + case 'b': + binary += "1011"; + break; + case 'c': + binary += "1100"; + break; + case 'd': + binary += "1101"; + break; + case 'e': + binary += "1110"; + break; + case 'f': + binary += "1111"; + break; + default: + throw std::invalid_argument("invalid hex character in string " + str); + } + } + } + // now convert the value + result.resize(binary.size()); + for (unsigned j = 0; j < binary.size(); j++) + result.preset(binary.size() - j - 1, binary[j] == '1'); + } + else + { + // sign-magnitude representation + // now scan for a sign and find whether this is a negative number + bool negative = false; + if (i < str.size()) + { + switch (str[i]) + { + case '-': + negative = true; + i++; + break; + case '+': + i++; + break; + } + } + for (; i < str.size(); i++) + { + result *= inf(radix); + unsigned char ascii = (unsigned char)str[i]; + int ch = from_char[ascii] ; + if (ch == -1) + throw std::invalid_argument("invalid decimal character in string " + str); + result += inf(ch); + } + if (negative) + result.negate(); + } + } + + //////////////////////////////////////////////////////////////////////////////// + // constructors - mostly implemented in terms of the assignment operators + + inf::inf(void) + { + // void constructor initialises to zero - represented as a single-byte value containing zero + m_data.append(1,std::string::value_type(0)); + } + + inf::inf(short r) + { + operator=(r); + } + + inf::inf(unsigned short r) + { + operator=(r); + } + + inf::inf(int r) + { + operator=(r); + } + + inf::inf(unsigned r) + { + operator=(r); + } + + inf::inf(long r) + { + operator=(r); + } + + inf::inf(unsigned long r) + { + operator=(r); + } + + inf::inf (const std::string& r) throw(std::invalid_argument) + { + operator=(r); + } + + inf::inf(const inf& r) + { +#ifdef __BORLANDC__ + // work round bug in Borland compiler - copy constructor fails if string + // contains null characters, so do my own copy + for (unsigned i = 0; i < r.m_data.size(); i++) + m_data += r.m_data[i]; +#else + m_data = r.m_data; +#endif + } + + //////////////////////////////////////////////////////////////////////////////// + + inf::~inf(void) + { + } + + //////////////////////////////////////////////////////////////////////////////// + // assignments convert from iteger types to internal representation + + inf& inf::operator = (short r) + { + convert_from_signed(r, m_data); + return *this; + } + + inf& inf::operator = (unsigned short r) + { + convert_from_unsigned(r, m_data); + return *this; + } + + inf& inf::operator = (int r) + { + convert_from_signed(r, m_data); + return *this; + } + + inf& inf::operator = (unsigned r) + { + convert_from_unsigned(r, m_data); + return *this; + } + + inf& inf::operator = (long r) + { + convert_from_signed(r, m_data); + return *this; + } + + inf& inf::operator = (unsigned long r) + { + convert_from_unsigned(r, m_data); + return *this; + } + + inf& inf::operator = (const std::string& r) throw(std::invalid_argument) + { + convert_from_string(r, *this); + return *this; + } + + inf& inf::operator = (const inf& r) + { + m_data = r.m_data; + return *this; + } + + //////////////////////////////////////////////////////////////////////////////// + + short inf::to_short(bool truncate) const throw(std::overflow_error) + { + short result = 0; + if (!convert_to_signed(m_data, result)) + if (!truncate) + throw std::overflow_error("stlplus::inf::to_short"); + return result; + } + + unsigned short inf::to_unsigned_short(bool truncate) const throw(std::overflow_error) + { + unsigned short result = 0; + if (!convert_to_unsigned(m_data, result)) + if (!truncate) + throw std::overflow_error("stlplus::inf::to_unsigned_short"); + return result; + } + + int inf::to_int(bool truncate) const throw(std::overflow_error) + { + int result = 0; + if (!convert_to_signed(m_data, result)) + if (!truncate) + throw std::overflow_error("stlplus::inf::to_int"); + return result; + } + + unsigned inf::to_unsigned(bool truncate) const throw(std::overflow_error) + { + unsigned result = 0; + if (!convert_to_unsigned(m_data, result)) + if (!truncate) + throw std::overflow_error("stlplus::inf::to_unsigned"); + return result; + } + + long inf::to_long(bool truncate) const throw(std::overflow_error) + { + long result = 0; + if (!convert_to_signed(m_data, result)) + if (!truncate) + throw std::overflow_error("stlplus::inf::to_long"); + return result; + } + + unsigned long inf::to_unsigned_long(bool truncate) const throw(std::overflow_error) + { + unsigned long result = 0; + if (!convert_to_unsigned(m_data, result)) + if (!truncate) + throw std::overflow_error("stlplus::inf::to_unsigned_long"); + return result; + } + + //////////////////////////////////////////////////////////////////////////////// + // resize the inf regardless of the data + + void inf::resize(unsigned bits) + { + if (bits == 0) bits = 1; + unsigned bytes = (bits+7)/8; + byte extend = negative() ? byte(255) : byte (0); + while(bytes > m_data.size()) + m_data.append(1,extend); + } + + // reduce the bit count to the minimum needed to preserve the value + + void inf::reduce(void) + { + reduce_string(m_data); + } + + //////////////////////////////////////////////////////////////////////////////// + // the number of significant bits in the number + + unsigned inf::bits (void) const + { + // The number of significant bits in the integer value - this is the number + // of indexable bits less any redundant sign bits at the msb + // This does not assume that the inf has been reduced to its minimum form + unsigned result = indexable_bits(); + bool sign = bit(result-1); + while (result > 1 && (sign == bit(result-2))) + result--; + return result; + } + + unsigned inf::size(void) const + { + return bits(); + } + + unsigned inf::indexable_bits (void) const + { + return 8 * unsigned(m_data.size()); + } + + //////////////////////////////////////////////////////////////////////////////// + // bitwise operations + + bool inf::bit (unsigned index) const throw(std::out_of_range) + { + if (index >= indexable_bits()) + throw std::out_of_range(std::string("stlplus::inf::bit")); + // first split the offset into byte offset and bit offset + unsigned byte_offset = index/8; + unsigned bit_offset = index%8; + return (byte(m_data[byte_offset]) & (byte(1) << bit_offset)) != 0; + } + + bool inf::operator [] (unsigned index) const throw(std::out_of_range) + { + return bit(index); + } + + void inf::set (unsigned index) throw(std::out_of_range) + { + if (index >= indexable_bits()) + throw std::out_of_range(std::string("stlplus::inf::set")); + // first split the offset into byte offset and bit offset + unsigned byte_offset = index/8; + unsigned bit_offset = index%8; + m_data[byte_offset] |= (byte(1) << bit_offset); + } + + void inf::clear (unsigned index) throw(std::out_of_range) + { + if (index >= indexable_bits()) + throw std::out_of_range(std::string("stlplus::inf::clear")); + // first split the offset into byte offset and bit offset + unsigned byte_offset = index/8; + unsigned bit_offset = index%8; + m_data[byte_offset] &= (~(byte(1) << bit_offset)); + } + + void inf::preset (unsigned index, bool value) throw(std::out_of_range) + { + if (value) + set(index); + else + clear(index); + } + + inf inf::slice(unsigned low, unsigned high) const throw(std::out_of_range) + { + if (low >= indexable_bits()) + throw std::out_of_range(std::string("stlplus::inf::slice: low index")); + if (high >= indexable_bits()) + throw std::out_of_range(std::string("stlplus::inf::slice: high index")); + inf result; + if (high >= low) + { + // create a result the right size and filled with sign bits + std::string::size_type result_size = (high-low+1+7)/8; + result.m_data.erase(); + byte extend = bit(high) ? byte(255) : byte (0); + while (result.m_data.size() < result_size) + result.m_data.append(1,extend); + // now set the relevant bits + for (unsigned i = low; i <= high; i++) + result.preset(i-low, bit(i)); + } + return result; + } + + //////////////////////////////////////////////////////////////////////////////// + // testing operations + + bool inf::negative (void) const + { + return bit(indexable_bits()-1); + } + + bool inf::natural (void) const + { + return !negative(); + } + + bool inf::positive (void) const + { + return natural() && !zero(); + } + + bool inf::zero (void) const + { + for (std::string::size_type i = 0; i < m_data.size(); i++) + if (m_data[i] != 0) + return false; + return true; + } + + bool inf::non_zero (void) const + { + return !zero(); + } + + bool inf::operator ! (void) const + { + return zero(); + } + + //////////////////////////////////////////////////////////////////////////////// + // comparison operators + + bool inf::operator == (const inf& r) const + { + // Two infs are equal if they are numerically equal, even if they are + // different sizes (i.e. they could be non-reduced values). + // This makes life a little more complicated than if I could assume that values were reduced. + byte l_extend = negative() ? byte(255) : byte (0); + byte r_extend = r.negative() ? byte(255) : byte (0); + std::string::size_type bytes = maximum(m_data.size(),r.m_data.size()); + for (std::string::size_type i = bytes; i--; ) + { + byte l_byte = (i < m_data.size() ? byte(m_data[i]) : l_extend); + byte r_byte = (i < r.m_data.size() ? byte(r.m_data[i]) : r_extend); + if (l_byte != r_byte) + return false; + } + return true; + } + + bool inf::operator != (const inf& r) const + { + return !operator==(r); + } + + bool inf::operator < (const inf& r) const + { + // This could be implemented in terms of subtraction. However, it can be + // simplified since there is no need to calculate the accurate difference, + // just the direction of the difference. I compare from msB down and as + // soon as a byte difference is found, that defines the ordering. The + // problem is that in 2's-complement, all negative values are greater than + // all natural values if you just do a straight unsigned comparison. I + // handle this by doing a preliminary test for different signs. + + // For example, a 3-bit signed type has the coding: + // 000 = 0 + // ... + // 011 = 3 + // 100 = -4 + // ... + // 111 = -1 + + // So, for natural values, the ordering of the integer values is the + // ordering of the bit patterns. Similarly, for negative values, the + // ordering of the integer values is the ordering of the bit patterns + // However, the bit patterns for the negative values are *greater than* + // the natural values. This is a side-effect of the naffness of + // 2's-complement representation + + // first handle the case of comparing two values with different signs + bool l_sign = negative(); + bool r_sign = r.negative(); + if (l_sign != r_sign) + { + // one argument must be negative and the other natural + // the left is less if it is the negative one + return l_sign; + } + // the arguments are the same sign + // so the ordering is a simple unsigned byte-by-byte comparison + // However, this is complicated by the possibility that the values could be different lengths + byte l_extend = l_sign ? byte(255) : byte (0); + byte r_extend = r_sign ? byte(255) : byte (0); + std::string::size_type bytes = maximum(m_data.size(),r.m_data.size()); + for (std::string::size_type i = bytes; i--; ) + { + byte l_byte = (i < m_data.size() ? byte(m_data[i]) : l_extend); + byte r_byte = (i < r.m_data.size() ? byte(r.m_data[i]) : r_extend); + if (l_byte != r_byte) + return l_byte < r_byte; + } + // if I get here, the two are equal, so that is not less-than + return false; + } + + bool inf::operator <= (const inf& r) const + { + return !(r < *this); + } + + bool inf::operator > (const inf& r) const + { + return r < *this; + } + + bool inf::operator >= (const inf& r) const + { + return !(*this < r); + } + + //////////////////////////////////////////////////////////////////////////////// + // logical operators + + inf& inf::invert (void) + { + for (std::string::size_type i = 0; i < m_data.size(); i++) + m_data[i] = ~m_data[i]; + return *this; + } + + inf inf::operator ~ (void) const + { + inf result(*this); + result.invert(); + return result; + } + + inf& inf::operator &= (const inf& r) + { + // bitwise AND is extended to the length of the largest argument + byte l_extend = negative() ? byte(255) : byte (0); + byte r_extend = r.negative() ? byte(255) : byte (0); + std::string::size_type bytes = maximum(m_data.size(),r.m_data.size()); + for (std::string::size_type i = 0; i < bytes; i++) + { + byte l_byte = (i < m_data.size() ? byte(m_data[i]) : l_extend); + byte r_byte = (i < r.m_data.size() ? byte(r.m_data[i]) : r_extend); + byte result = l_byte & r_byte; + if (i < m_data.size()) + m_data[i] = result; + else + m_data.append(1,result); + } + // now reduce the result + reduce(); + return *this; + } + + inf inf::operator & (const inf& r) const + { + inf result(*this); + result &= r; + return result; + } + + inf& inf::operator |= (const inf& r) + { + // bitwise OR is extended to the length of the largest argument + byte l_extend = negative() ? byte(255) : byte (0); + byte r_extend = r.negative() ? byte(255) : byte (0); + std::string::size_type bytes = maximum(m_data.size(),r.m_data.size()); + for (std::string::size_type i = 0; i < bytes; i++) + { + byte l_byte = (i < m_data.size() ? byte(m_data[i]) : l_extend); + byte r_byte = (i < r.m_data.size() ? byte(r.m_data[i]) : r_extend); + byte result = l_byte | r_byte; + if (i < m_data.size()) + m_data[i] = result; + else + m_data.append(1,result); + } + // now reduce the result + reduce(); + return *this; + } + + inf inf::operator | (const inf& r) const + { + inf result(*this); + result |= r; + return result; + } + + inf& inf::operator ^= (const inf& r) + { + // bitwise XOR is extended to the length of the largest argument + byte l_extend = negative() ? byte(255) : byte (0); + byte r_extend = r.negative() ? byte(255) : byte (0); + std::string::size_type bytes = maximum(m_data.size(),r.m_data.size()); + for (std::string::size_type i = 0; i < bytes; i++) + { + byte l_byte = (i < m_data.size() ? byte(m_data[i]) : l_extend); + byte r_byte = (i < r.m_data.size() ? byte(r.m_data[i]) : r_extend); + byte result = l_byte ^ r_byte; + if (i < m_data.size()) + m_data[i] = result; + else + m_data.append(1,result); + } + // now reduce the result + reduce(); + return *this; + } + + inf inf::operator ^ (const inf& r) const + { + inf result(*this); + result ^= r; + return result; + } + + //////////////////////////////////////////////////////////////////////////////// + // shift operators all preserve the value by increasing the word size + + inf& inf::operator <<= (unsigned shift) + { + // left shift is a shift towards the msb, with 0s being shifted in at the lsb + // split this into a byte shift followed by a bit shift + + // first expand the value to be big enough for the result + std::string::size_type new_size = (indexable_bits() + shift + 7) / 8; + byte extend = negative() ? byte(255) : byte (0); + while (m_data.size() < new_size) + m_data.append(1,extend); + // now do the byte shift + unsigned byte_shift = shift/8; + if (byte_shift > 0) + { + for (std::string::size_type b = new_size; b--; ) + m_data[b] = (b >= byte_shift) ? m_data[b-byte_shift] : byte(0); + } + // and finally the bit shift + unsigned bit_shift = shift%8; + if (bit_shift > 0) + { + for (std::string::size_type b = new_size; b--; ) + { + byte current = byte(m_data[b]); + byte previous = b > 0 ? m_data[b-1] : byte(0); + m_data[b] = (current << bit_shift) | (previous >> (8 - bit_shift)); + } + } + // now reduce the result + reduce(); + return *this; + } + + inf inf::operator << (unsigned shift) const + { + inf result(*this); + result <<= shift; + return result; + } + + inf& inf::operator >>= (unsigned shift) + { + // right shift is a shift towards the lsb, with sign bits being shifted in at the msb + // split this into a byte shift followed by a bit shift + + // a byte of sign bits + byte extend = negative() ? byte(255) : byte (0); + // do the byte shift + unsigned byte_shift = shift/8; + if (byte_shift > 0) + { + for (std::string::size_type b = 0; b < m_data.size(); b++) + m_data[b] = (b + byte_shift < m_data.size()) ? m_data[b+byte_shift] : extend; + } + // and finally the bit shift + unsigned bit_shift = shift%8; + if (bit_shift > 0) + { + for (std::string::size_type b = 0; b < m_data.size(); b++) + { + byte current = byte(m_data[b]); + byte next = ((b+1) < m_data.size()) ? m_data[b+1] : extend; + byte shifted = (current >> bit_shift) | (next << (8 - bit_shift)); + m_data[b] = shifted; + } + } + // now reduce the result + reduce(); + return *this; + } + + inf inf::operator >> (unsigned shift) const + { + inf result(*this); + result >>= shift; + return result; + } + + //////////////////////////////////////////////////////////////////////////////// + // negation operators + + inf& inf::negate (void) + { + // do 2's-complement negation + // equivalent to inversion plus one + invert(); + operator += (inf(1)); + return *this; + } + + inf inf::operator - (void) const + { + inf result(*this); + result.negate(); + return result; + } + + inf& inf::abs(void) + { + if (negative()) negate(); + return *this; + } + + inf abs(const inf& i) + { + inf result = i; + result.abs(); + return result; + } + + //////////////////////////////////////////////////////////////////////////////// + // addition operators + + inf& inf::operator += (const inf& r) + { + // do 2's-complement addition + // Note that the addition can give a result that is larger than either argument + byte carry = 0; + std::string::size_type max_size = maximum(m_data.size(),r.m_data.size()); + byte l_extend = negative() ? byte(255) : byte (0); + byte r_extend = r.negative() ? byte(255) : byte (0); + for (std::string::size_type i = 0; i < max_size; i++) + { + byte l_byte = (i < m_data.size() ? byte(m_data[i]) : l_extend); + byte r_byte = (i < r.m_data.size() ? byte(r.m_data[i]) : r_extend); + // calculate the addition in a type that is bigger than a byte in order to catch the carry-out + unsigned short result = ((unsigned short)(l_byte)) + ((unsigned short)(r_byte)) + carry; + // now truncate the result to get the lsB + if (i < m_data.size()) + m_data[i] = byte(result); + else + m_data.append(1,byte(result)); + // and capture the carry out by grabbing the second byte of the result + carry = byte(result >> 8); + } + // if the result overflowed or underflowed, add an extra byte to catch it + unsigned short result = ((unsigned short)(l_extend)) + ((unsigned short)(r_extend)) + carry; + if (byte(result) != (negative() ? byte(255) : byte(0))) + m_data.append(1,byte(result)); + // now reduce the result + reduce(); + return *this; + } + + inf inf::operator + (const inf& r) const + { + inf result(*this); + result += r; + return result; + } + + //////////////////////////////////////////////////////////////////////////////// + // subtraction operators + + inf& inf::operator -= (const inf& r) + { + // subtraction is defined in terms of negation and addition + inf negated = -r; + operator += (negated); + return *this; + } + + inf inf::operator - (const inf& r) const + { + inf result(*this); + result -= r; + return result; + } + + //////////////////////////////////////////////////////////////////////////////// + // multiplication operators + + inf& inf::operator *= (const inf& r) + { + // 2's complement multiplication + // one day I'll do a more efficient version than this based on the underlying representation + inf left(*this); + inf right = r; + // make the right value natural but preserve its sign for later + bool right_negative = right.negative(); + right.abs(); + // implemented as a series of conditional additions + operator = (0); + // left.resize(right.bits() + left.bits() - 1); + left <<= right.bits()-1; + for (unsigned i = right.bits(); i--; ) + { + if (right[i]) + operator += (left); + left >>= 1; + } + if (right_negative) + negate(); + // now reduce the result + reduce(); + return *this; + } + + inf inf::operator * (const inf& r) const + { + inf result(*this); + result *= r; + return result; + } + + //////////////////////////////////////////////////////////////////////////////// + // division and remainder operators + + std::pair inf::divide(const inf& right) const throw(divide_by_zero) + { + if (right.zero()) + throw divide_by_zero("stlplus::inf::divide"); + inf numerator(*this); + inf denominator = right; + // make the numerator natural but preserve the sign for later + bool numerator_negative = numerator.negative(); + numerator.abs(); + // same with the denominator + bool denominator_negative = denominator.negative(); + denominator.abs(); + // the quotient and remainder will form the result + // start with the quotiont zero and the remainder equal to the whole of the + // numerator, then do trial subtraction from this + inf quotient; + inf remainder = numerator; + // there's nothing more to do if the numerator is smaller than the denominator + // but otherwise do the division + if (numerator.bits() >= denominator.bits()) + { + // make the quotient big enough to take the result + quotient.resize(numerator.bits()); + // start with the numerator shifted to the far left + unsigned shift = numerator.bits() - denominator.bits(); + denominator <<= shift; + // do the division by repeated subtraction, + for (unsigned i = shift+1; i--; ) + { + if (remainder >= denominator) + { + remainder -= denominator; + quotient.set(i); + } + denominator >>= 1; + } + } + // now adjust the signs + // x/(-y) == (-x)/y == -(x/y) + if (numerator_negative != denominator_negative) + quotient.negate(); + quotient.reduce(); + // x%(-y) == x%y and (-x)%y == -(x%y) + if (numerator_negative) + remainder.negate(); + remainder.reduce(); + return std::pair(quotient,remainder); + } + + inf& inf::operator /= (const inf& r) throw(divide_by_zero) + { + std::pair result = divide(r); + operator=(result.first); + return *this; + } + + inf inf::operator / (const inf& r) const throw(divide_by_zero) + { + std::pair result = divide(r); + return result.first; + } + + inf& inf::operator %= (const inf& r) throw(divide_by_zero) + { + std::pair result = divide(r); + operator=(result.second); + return *this; + } + + inf inf::operator % (const inf& r) const throw(divide_by_zero) + { + std::pair result = divide(r); + return result.second; + } + + //////////////////////////////////////////////////////////////////////////////// + // prefix (void) and postfix (int) operators + + inf& inf::operator ++ (void) + { + operator += (inf(1)); + return *this; + } + + inf inf::operator ++ (int) + { + inf old(*this); + operator += (inf(1)); + return old; + } + + inf& inf::operator -- (void) + { + operator -= (inf(1)); + return *this; + } + + inf inf::operator -- (int) + { + inf old(*this); + operator -= (inf(1)); + return old; + } + + //////////////////////////////////////////////////////////////////////////////// + // string representation and I/O routines + + std::string inf::to_string(unsigned radix) const + throw(std::invalid_argument) + { + std::string result; + convert_to_string(*this, result, radix); + return result; + } + + inf& inf::from_string(const std::string& value, unsigned radix) + throw(std::invalid_argument) + { + convert_from_string(value, *this, radix); + return *this; + } + + std::ostream& operator << (std::ostream& str, const inf& i) + { + try + { + // get radix + unsigned radix = 10; + if (str.flags() & std::ios_base::oct) + radix = 8; + if (str.flags() & std::ios_base::hex) + radix = 16; + // the field width is handled by iostream, so I don't need to handle it as well + // generate the string representation then print it + str << i.to_string(radix); + } + catch(const std::invalid_argument) + { + str.setstate(std::ios_base::badbit); + } + return str; + } + + std::istream& operator >> (std::istream& str, inf& i) + { + try + { + // get radix + unsigned radix = 10; + if (str.flags() & std::ios_base::oct) + radix = 8; + if (str.flags() & std::ios_base::hex) + radix = 16; + // now get the string image of the value + std::string image; + str >> image; + // and convert to inf + i.from_string(image, radix); + } + catch(const std::invalid_argument) + { + str.setstate(std::ios_base::badbit); + } + return str; + } + + //////////////////////////////////////////////////////////////////////////////// + // diagnostic dump + // just convert to hex + + std::string inf::image_debug(void) const + { + // create this dump in the human-readable form, i.e. msB to the left + std::string result = "0x"; + for (std::string::size_type i = m_data.size(); i--; ) + { + byte current = m_data[i]; + byte msB = (current & byte(0xf0)) >> 4; + result += to_char[msB]; + byte lsB = (current & byte(0x0f)); + result += to_char[lsB]; + } + return result; + } + + const std::string& inf::get_bytes(void) const + { + return m_data; + } + + void inf::set_bytes(const std::string& data) + { + m_data = data; + } + +} // end namespace stlplus diff --git a/src/stlplus/portability/inf.hpp b/src/stlplus/portability/inf.hpp index c20acd4..831a56e 100644 --- a/src/stlplus/portability/inf.hpp +++ b/src/stlplus/portability/inf.hpp @@ -1,228 +1,228 @@ -#ifndef STLPLUS_INF -#define STLPLUS_INF -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// An infinite-precision integer class. This allows calculations on large -// integers to be performed without overflow. - -// this class can throw the following exceptions: -// std::out_of_range -// std::overflow_error -// std::invalid_argument -// stlplus::divide_by_zero // why doesn't std have this? -// all of these are derivations of the baseclass: -// std::logic_error -// So you can catch all of them by catching the baseclass - -// Warning: inf was never intended to be fast, it is just for programs which -// need a bit of infinite-precision integer arithmetic. For high-performance -// processing, use the Gnu Multi-Precision (GMP) library. The inf type is just -// easier to integrate and is already ported to all platforms and compilers -// that STLplus is ported to. - -//////////////////////////////////////////////////////////////////////////////// -#include "portability_fixes.hpp" -#include "portability_exceptions.hpp" -#include -#include - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - -//////////////////////////////////////////////////////////////////////////////// - - class inf - { - public: - - ////////////////////////////////////////////////////////////////////////////// - // constructors and assignments initialise the inf - - // the void constructor initialises to zero, the others initialise to the - // value of the C integer type or the text value contained in the string - - inf(void); - explicit inf(short); - explicit inf(unsigned short); - explicit inf(int); - explicit inf(unsigned); - explicit inf(long); - explicit inf(unsigned long); - explicit inf(const std::string&) throw(std::invalid_argument); - inf(const inf&); - - ~inf(void); - - // assignments with equivalent behaviour to the constructors - - inf& operator = (short); - inf& operator = (unsigned short); - inf& operator = (int); - inf& operator = (unsigned); - inf& operator = (long); - inf& operator = (unsigned long); - inf& operator = (const std::string&) throw(std::invalid_argument); - inf& operator = (const inf&); - - ////////////////////////////////////////////////////////////////////////////// - // conversions back to the C types - // truncate: controls the behaviour when the value is too long for the result - // true: truncate the value - // false: throw an exception - - short to_short(bool truncate = true) const throw(std::overflow_error); - unsigned short to_unsigned_short(bool truncate = true) const throw(std::overflow_error); - - int to_int(bool truncate = true) const throw(std::overflow_error); - unsigned to_unsigned(bool truncate = true) const throw(std::overflow_error); - - long to_long(bool truncate = true) const throw(std::overflow_error); - unsigned long to_unsigned_long(bool truncate = true) const throw(std::overflow_error); - - ////////////////////////////////////////////////////////////////////////////// - // bitwise manipulation - - void resize(unsigned bits); - void reduce(void); - - // the number of significant bits in the value - unsigned bits (void) const; - unsigned size (void) const; - - // the number of bits that can be accessed by the bit() method (=bits() rounded up to the next byte) - unsigned indexable_bits(void) const; - - bool bit (unsigned index) const throw(std::out_of_range); - bool operator [] (unsigned index) const throw(std::out_of_range); - - void set (unsigned index) throw(std::out_of_range); - void clear (unsigned index) throw(std::out_of_range); - void preset (unsigned index, bool value) throw(std::out_of_range); - - inf slice(unsigned low, unsigned high) const throw(std::out_of_range); - - ////////////////////////////////////////////////////////////////////////////// - // tests for common values or ranges - - bool negative (void) const; - bool natural (void) const; - bool positive (void) const; - bool zero (void) const; - bool non_zero (void) const; - - // tests used in if(i) and if(!i) -// operator bool (void) const; - bool operator ! (void) const; - - ////////////////////////////////////////////////////////////////////////////// - // comparisons - - bool operator == (const inf&) const; - bool operator != (const inf&) const; - bool operator < (const inf&) const; - bool operator <= (const inf&) const; - bool operator > (const inf&) const; - bool operator >= (const inf&) const; - - ////////////////////////////////////////////////////////////////////////////// - // bitwise logic operations - - inf& invert (void); - inf operator ~ (void) const; - - inf& operator &= (const inf&); - inf operator & (const inf&) const; - - inf& operator |= (const inf&); - inf operator | (const inf&) const; - - inf& operator ^= (const inf&); - inf operator ^ (const inf&) const; - - inf& operator <<= (unsigned shift); - inf operator << (unsigned shift) const; - - inf& operator >>= (unsigned shift); - inf operator >> (unsigned shift) const; - - ////////////////////////////////////////////////////////////////////////////// - // arithmetic operations - - inf& negate (void); - inf operator - (void) const; - - inf& abs(void); - friend inf abs(const inf&); - - inf& operator += (const inf&); - inf operator + (const inf&) const; - - inf& operator -= (const inf&); - inf operator - (const inf&) const; - - inf& operator *= (const inf&); - inf operator * (const inf&) const; - - inf& operator /= (const inf&) throw(divide_by_zero); - inf operator / (const inf&) const throw(divide_by_zero); - - inf& operator %= (const inf&) throw(divide_by_zero); - inf operator % (const inf&) const throw(divide_by_zero); - - // combined division operator - returns the result pair(quotient,remainder) in one go - std::pair divide(const inf&) const throw(divide_by_zero); - - ////////////////////////////////////////////////////////////////////////////// - // pre- and post- increment and decrement - - inf& operator ++ (void); - inf operator ++ (int); - inf& operator -- (void); - inf operator -- (int); - - ////////////////////////////////////////////////////////////////////////////// - // string representation and I/O - - std::string image_debug(void) const; - - // conversion to a string representation - // radix must be 10, 2, 8 or 16 - std::string to_string(unsigned radix = 10) const - throw(std::invalid_argument); - - // conversion from a string - // radix == 0 - radix is deduced from the input - assumed 10 unless number is prefixed by 0b, 0 or 0x - // however, you can specify the radix to be 10, 2, 8 or 16 to force that interpretation - inf& from_string(const std::string&, unsigned radix = 0) - throw(std::invalid_argument); - - ////////////////////////////////////////////////////////////////////////////// - private: - std::string m_data; - public: - const std::string& get_bytes(void) const; - void set_bytes(const std::string&); - }; - - //////////////////////////////////////////////////////////////////////////////// - // redefine friends for gcc v4.1 - - inf abs(const inf&); - - //////////////////////////////////////////////////////////////////////////////// - - std::ostream& operator << (std::ostream&, const inf&); - std::istream& operator >> (std::istream&, inf&); - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#endif +#ifndef STLPLUS_INF +#define STLPLUS_INF +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// An infinite-precision integer class. This allows calculations on large +// integers to be performed without overflow. + +// this class can throw the following exceptions: +// std::out_of_range +// std::overflow_error +// std::invalid_argument +// stlplus::divide_by_zero // why doesn't std have this? +// all of these are derivations of the baseclass: +// std::logic_error +// So you can catch all of them by catching the baseclass + +// Warning: inf was never intended to be fast, it is just for programs which +// need a bit of infinite-precision integer arithmetic. For high-performance +// processing, use the Gnu Multi-Precision (GMP) library. The inf type is just +// easier to integrate and is already ported to all platforms and compilers +// that STLplus is ported to. + +//////////////////////////////////////////////////////////////////////////////// +#include "portability_fixes.hpp" +#include "portability_exceptions.hpp" +#include +#include + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + +//////////////////////////////////////////////////////////////////////////////// + + class inf + { + public: + + ////////////////////////////////////////////////////////////////////////////// + // constructors and assignments initialise the inf + + // the void constructor initialises to zero, the others initialise to the + // value of the C integer type or the text value contained in the string + + inf(void); + explicit inf(short); + explicit inf(unsigned short); + explicit inf(int); + explicit inf(unsigned); + explicit inf(long); + explicit inf(unsigned long); + explicit inf(const std::string&) throw(std::invalid_argument); + inf(const inf&); + + ~inf(void); + + // assignments with equivalent behaviour to the constructors + + inf& operator = (short); + inf& operator = (unsigned short); + inf& operator = (int); + inf& operator = (unsigned); + inf& operator = (long); + inf& operator = (unsigned long); + inf& operator = (const std::string&) throw(std::invalid_argument); + inf& operator = (const inf&); + + ////////////////////////////////////////////////////////////////////////////// + // conversions back to the C types + // truncate: controls the behaviour when the value is too long for the result + // true: truncate the value + // false: throw an exception + + short to_short(bool truncate = true) const throw(std::overflow_error); + unsigned short to_unsigned_short(bool truncate = true) const throw(std::overflow_error); + + int to_int(bool truncate = true) const throw(std::overflow_error); + unsigned to_unsigned(bool truncate = true) const throw(std::overflow_error); + + long to_long(bool truncate = true) const throw(std::overflow_error); + unsigned long to_unsigned_long(bool truncate = true) const throw(std::overflow_error); + + ////////////////////////////////////////////////////////////////////////////// + // bitwise manipulation + + void resize(unsigned bits); + void reduce(void); + + // the number of significant bits in the value + unsigned bits (void) const; + unsigned size (void) const; + + // the number of bits that can be accessed by the bit() method (=bits() rounded up to the next byte) + unsigned indexable_bits(void) const; + + bool bit (unsigned index) const throw(std::out_of_range); + bool operator [] (unsigned index) const throw(std::out_of_range); + + void set (unsigned index) throw(std::out_of_range); + void clear (unsigned index) throw(std::out_of_range); + void preset (unsigned index, bool value) throw(std::out_of_range); + + inf slice(unsigned low, unsigned high) const throw(std::out_of_range); + + ////////////////////////////////////////////////////////////////////////////// + // tests for common values or ranges + + bool negative (void) const; + bool natural (void) const; + bool positive (void) const; + bool zero (void) const; + bool non_zero (void) const; + + // tests used in if(i) and if(!i) +// operator bool (void) const; + bool operator ! (void) const; + + ////////////////////////////////////////////////////////////////////////////// + // comparisons + + bool operator == (const inf&) const; + bool operator != (const inf&) const; + bool operator < (const inf&) const; + bool operator <= (const inf&) const; + bool operator > (const inf&) const; + bool operator >= (const inf&) const; + + ////////////////////////////////////////////////////////////////////////////// + // bitwise logic operations + + inf& invert (void); + inf operator ~ (void) const; + + inf& operator &= (const inf&); + inf operator & (const inf&) const; + + inf& operator |= (const inf&); + inf operator | (const inf&) const; + + inf& operator ^= (const inf&); + inf operator ^ (const inf&) const; + + inf& operator <<= (unsigned shift); + inf operator << (unsigned shift) const; + + inf& operator >>= (unsigned shift); + inf operator >> (unsigned shift) const; + + ////////////////////////////////////////////////////////////////////////////// + // arithmetic operations + + inf& negate (void); + inf operator - (void) const; + + inf& abs(void); + friend inf abs(const inf&); + + inf& operator += (const inf&); + inf operator + (const inf&) const; + + inf& operator -= (const inf&); + inf operator - (const inf&) const; + + inf& operator *= (const inf&); + inf operator * (const inf&) const; + + inf& operator /= (const inf&) throw(divide_by_zero); + inf operator / (const inf&) const throw(divide_by_zero); + + inf& operator %= (const inf&) throw(divide_by_zero); + inf operator % (const inf&) const throw(divide_by_zero); + + // combined division operator - returns the result pair(quotient,remainder) in one go + std::pair divide(const inf&) const throw(divide_by_zero); + + ////////////////////////////////////////////////////////////////////////////// + // pre- and post- increment and decrement + + inf& operator ++ (void); + inf operator ++ (int); + inf& operator -- (void); + inf operator -- (int); + + ////////////////////////////////////////////////////////////////////////////// + // string representation and I/O + + std::string image_debug(void) const; + + // conversion to a string representation + // radix must be 10, 2, 8 or 16 + std::string to_string(unsigned radix = 10) const + throw(std::invalid_argument); + + // conversion from a string + // radix == 0 - radix is deduced from the input - assumed 10 unless number is prefixed by 0b, 0 or 0x + // however, you can specify the radix to be 10, 2, 8 or 16 to force that interpretation + inf& from_string(const std::string&, unsigned radix = 0) + throw(std::invalid_argument); + + ////////////////////////////////////////////////////////////////////////////// + private: + std::string m_data; + public: + const std::string& get_bytes(void) const; + void set_bytes(const std::string&); + }; + + //////////////////////////////////////////////////////////////////////////////// + // redefine friends for gcc v4.1 + + inf abs(const inf&); + + //////////////////////////////////////////////////////////////////////////////// + + std::ostream& operator << (std::ostream&, const inf&); + std::istream& operator >> (std::istream&, inf&); + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#endif diff --git a/src/stlplus/portability/ip_sockets.cpp b/src/stlplus/portability/ip_sockets.cpp index 0db880e..d5d40de 100644 --- a/src/stlplus/portability/ip_sockets.cpp +++ b/src/stlplus/portability/ip_sockets.cpp @@ -1,961 +1,965 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Contains all the platform-specific socket handling used by the TCP and UDP classes - -// TODO - any conversion required to support IPv6 - -//////////////////////////////////////////////////////////////////////////////// - -#include "ip_sockets.hpp" -#include "dprintf.hpp" -#include - -#ifdef MSWINDOWS -// Windoze-specific includes -#include -#define ERRNO WSAGetLastError() -#define HERRNO WSAGetLastError() -#define IOCTL ioctlsocket -#define CLOSE closesocket -#define SHUT_RDWR SD_BOTH -#define EINPROGRESS WSAEINPROGRESS -#define EWOULDBLOCK WSAEWOULDBLOCK -#define ECONNRESET WSAECONNRESET -#define SOCKLEN_T int -#else -// Generic Unix includes -// fix for older versions of Darwin? -#define _BSD_SOCKLEN_T_ int -#include -#include -#include -#include -#include -#include -#include -#include -#define INVALID_SOCKET -1 -#define ERRNO errno -#define HERRNO h_errno -#define SOCKET int -#define SOCKET_ERROR -1 -#define IOCTL ::ioctl -#define CLOSE ::close -#define SOCKLEN_T socklen_t -#ifdef SOLARIS -// Sun put some definitions in a different place -#include -#endif -#endif - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // Utilities - - // get an operating-system error message given an error code - static std::string error_string(int error) - { - std::string result = "error " + dformat("%d",error); -#ifdef MSWINDOWS - char* message = 0; - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, - 0, - error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // "User default language" - (LPTSTR)&message, - 0,0); - if (message) - { - result = message; - LocalFree(message); - } - // the error message is for some perverse reason newline terminated - remove this - if (result[result.size()-1] == '\n') - result.erase(result.end()-1); - if (result[result.size()-1] == '\r') - result.erase(result.end()-1); -#else - char* message = strerror(error); - if (message && message[0]) - result = message; -#endif - return result; - } - - // convert address:port into a sockaddr - static void convert_address(unsigned long address, unsigned short port, sockaddr& sa) - { - sa.sa_family = AF_INET; - unsigned short network_port = htons(port); - memcpy(&sa.sa_data[0], &network_port, sizeof(network_port)); - unsigned long network_address = htonl(address); - memcpy(&sa.sa_data[2], &network_address, sizeof(network_address)); - } - -// // convert host:port into a sockaddr -// static void convert_host(hostent& host, unsigned short port, sockaddr& sa) -// { -// sa.sa_family = host.h_addrtype; -// unsigned short network_port = htons(port); -// memcpy(&sa.sa_data[0], &network_port, sizeof(network_port)); -// memcpy(&sa.sa_data[2], host.h_addr, host.h_length); -// } - - // convert sockaddr to address:port - static void convert_sockaddr(const sockaddr& sa, unsigned long& address, unsigned short& port) - { - unsigned short network_port = 0; - memcpy(&network_port, &sa.sa_data[0], sizeof(network_port)); - port = ntohs(network_port); - unsigned long network_address = 0; - memcpy(&network_address, &sa.sa_data[2], sizeof(network_address)); - address = ntohl(network_address); - } - - //////////////////////////////////////////////////////////////////////////////// - // Initialisation - // Windows requires that Winsock is initialised before use and closed after - // These routines initialise once on first use and close on the destruction of the last object using it - // on non-windows platforms, I still increment/decrement the sockets count variable for diagnostic purposes - - static int sockets_count = 0; - - static int sockets_init(void) - { - int error = 0; - if (sockets_count++ == 0) - { -#ifdef MSWINDOWS - WSAData winsock_info; - // request Winsock 2.0 or higher - error = WSAStartup(MAKEWORD(2,0),&winsock_info); -#endif - } - return error; - } - - static int sockets_close(void) - { - int error = 0; - if (--sockets_count == 0) - { -#ifdef MSWINDOWS - if (WSACleanup() == SOCKET_ERROR) - error = ERRNO; -#endif - } - return error; - } - - //////////////////////////////////////////////////////////////////////////////// - // Socket Implementation - common code to manipulate a TCP socket - - class IP_socket_internals - { - private: - IP_socket_type m_type; - SOCKET m_socket; - unsigned long m_remote_address; - unsigned short m_remote_port; - mutable int m_error; - mutable std::string m_message; - unsigned m_count; - - // disable copying of the internals - IP_socket_internals(const IP_socket_internals&); - IP_socket_internals& operator=(const IP_socket_internals&); - - public: - - //////////////////////////////////////////////////////////////////////////// - // PIMPL alias counting - - void increment(void) - { - ++m_count; - } - - bool decrement(void) - { - --m_count; - return m_count == 0; - } - - //////////////////////////////////////////////////////////////////////////// - // constructors/destructors - - // construct an invalid socket - IP_socket_internals(void) : m_type(undefined_socket_type), m_socket(INVALID_SOCKET), m_error(0), m_count(1) - { - set_error(sockets_init()); - } - - // close on destroy - ~IP_socket_internals(void) - { - close(); - set_error(sockets_close()); - } - - //////////////////////////////////////////////////////////////////////////// - // initialisation, connection - - bool initialised(void) const - { - return m_socket != INVALID_SOCKET; - } - - // attach this object to a pre-opened socket - bool set(SOCKET socket, unsigned long remote_address, unsigned short remote_port) - { - if (initialised()) close(); - clear_error(); - m_socket = socket; - m_remote_address = remote_address; - m_remote_port = remote_port; - return true; - } - - // create a raw socket attached to this object - bool initialise(IP_socket_type type) - { - if (initialised()) close(); - clear_error(); - if ((type != TCP) && (type != UDP)) - { - set_error(-1, "Illegal socket type"); - return false; - } - // create an anonymous socket - m_socket = ::socket(AF_INET, ((type == TCP) ? SOCK_STREAM : SOCK_DGRAM), 0); - if (m_socket == INVALID_SOCKET) - { - set_error(ERRNO); - close(); - return false; - } - // record the type on success only - m_type = type; - // set the socket into non-blocking mode - unsigned long nonblocking = 1; - if (IOCTL(m_socket, FIONBIO, &nonblocking) == SOCKET_ERROR) - { - set_error(ERRNO); - return false; - } - return true; - } - - // function for performing IP lookup (i.e. gethostbyname) - // could be standalone but making it a member means that it can use the socket's error handler - // - remote_address: IP name or number - // - returns the IP address as a number - zero if there's an error - unsigned long ip_lookup(const std::string& remote_address) - { - unsigned long result = 0; - // Lookup the IP address to convert it into a host record - // this DOES lookup IP address names as well (not according to MS help !!) - // TODO - convert this to use ::getaddrinfo - ::gethostbyname is deprecated - hostent* host_info = ::gethostbyname(remote_address.c_str()); - if (!host_info) - { - set_error(HERRNO); - return 0; - } - // extract the address from the host info - unsigned long network_address = 0; - memcpy(&network_address, host_info->h_addr, host_info->h_length); - result = ntohl(network_address); - return result; - } - - // tests whether a socket is ready for communication - bool select(bool readable, bool writeable, unsigned wait) - { - if (!initialised()) return false; - // set up the readable set - fd_set readable_set; - fd_set* readable_set_ptr = 0; - if (readable) - { - FD_ZERO(&readable_set); - FD_SET(m_socket,&readable_set); - readable_set_ptr = &readable_set; - } - // set up the writeable set - fd_set writeable_set; - fd_set* writeable_set_ptr = 0; - if (writeable) - { - FD_ZERO(&writeable_set); - FD_SET(m_socket,&writeable_set); - writeable_set_ptr = &writeable_set; - } - // TODO - check the error set and lookup the error? - fd_set* error_set_ptr = 0; - // set up the timout value - // Note: a null pointer implements a blocking select - // a pointer to a zero value implements a zero-wait poll - // a pointer to a positive value implements a poll with a timeout - // I currently only implement polling with timeout which may be zero - no blocking - timeval timeout; - timeval* timeout_ptr = 0; - timeout.tv_sec = wait/1000000; - timeout.tv_usec = wait%1000000; - timeout_ptr = &timeout; - // now test the socket - int select_result = ::select(m_socket+1, readable_set_ptr, writeable_set_ptr, error_set_ptr, timeout_ptr); - switch(select_result) - { - case SOCKET_ERROR: - // select failed with an error - trap the error - set_error(ERRNO); - return false; - case 0: - // timeout exceeded without a connection appearing - return false; - default: - // at least one connection is pending - // TODO - do we need to do the extra socket options checking on Posix? - // TODO - does this connect in any way to the error_set above? - return true; - } - } - - // bind the socket to a port so that it can receive from specific address - bool bind(unsigned long remote_address, unsigned short local_port) - { - if (!initialised()) return false; - // name the socket and bind it to a port - this is a requirement for a server - sockaddr server; - convert_address(INADDR_ANY, local_port, server); - if (::bind(m_socket, &server, sizeof(server)) == SOCKET_ERROR) - { - set_error(ERRNO); - close(); - return false; - } - return true; - } - - // bind the socket to a port so that it can receive from any address - bool bind_any(unsigned short local_port) - { - return bind(INADDR_ANY, local_port); - } - - // set this socket up to be a listening port - // must have been bound to a local port already - // - length of backlog queue to manage - may be zero - // - returns success status - bool listen(unsigned short queue) - { - if (!initialised()) return false; - // set the port to listen for incoming connections - if (::listen(m_socket, (int)queue) == SOCKET_ERROR) - { - set_error(ERRNO); - close(); - return false; - } - return true; - } - - // test whether there's an incoming connection on the socket - // only applicable if it has been set up as a listening port - bool accept_ready(unsigned wait) - { - // the test for a connection being ready is the same as the test for whether the socket is readable - // see documentation for select - return select(true, false, wait); - } - - // accept a connection on the socket - // only applicable if it has been set up as a listening port - // - returns socket filled in with the accepted connection's details - or with the error fields set - IP_socket accept(void) - { - if (!initialised()) return IP_socket(); - IP_socket result; - // accept the connection, at the same time getting the address of the connecting client - sockaddr saddress; - SOCKLEN_T saddress_length = sizeof(saddress); - SOCKET socket = ::accept(m_socket, &saddress, &saddress_length); - if (socket == INVALID_SOCKET) - { - // only set the result socket with an error - result.m_impl->set_error(ERRNO); - return result; - } - // extract the contents of the address - unsigned long remote_address = 0; - unsigned short remote_port = 0; - convert_sockaddr(saddress, remote_address, remote_port); - result.m_impl->set(socket, remote_address, remote_port); - return result; - } - - // client connect to a server - // - remote_address: IP number of remote address to connect to - // - remote_port: port to connect to - bool connect(unsigned long remote_address, unsigned short remote_port) - { - if (!initialised()) return false; - // fill in the connection data structure - sockaddr connect_data; - convert_address(remote_address, remote_port, connect_data); - // connect binds the socket to a local address - // if connectionless it simply sets the default remote address - // if connectioned it makes the connection - if (::connect(m_socket, &connect_data, sizeof(connect_data)) == SOCKET_ERROR) - { - // the socket is non-blocking, so connect will almost certainly fail with EINPROGRESS which is not an error - // only catch real errors - int error = ERRNO; - if (error != EINPROGRESS && error != EWOULDBLOCK) - { - set_error(error); - return false; - } - } - // extract the remote connection details for local storage - convert_sockaddr(connect_data, m_remote_address, m_remote_port); - return true; - } - - // test whether a socket is connected and ready to communicate - bool connected(unsigned wait) - { - if (!initialised()) return false; - // Linux and Windows docs say test with select for whether socket is - // writable. However, a problem has been reported with Linux whereby - // the OS will report a socket as writable when it isn't - // first use the select method - if (!select(false, true, wait)) - return false; -#ifdef MSWINDOWS - // Windows needs no further processing - select method works - return true; -#else - // Posix version needs further checking using the socket options - // DJDM: socket has returned EINPROGRESS on the first attempt at connection - // it has also returned that it can be written to - // we must now ask it if it has actually connected - using getsockopt - int error = 0; - socklen_t serror = sizeof(int); - if (::getsockopt(m_socket, SOL_SOCKET, SO_ERROR, &error, &serror)==0) - // handle the error value - one of them means that the socket has connected - if (!error || error == EISCONN) - return true; - return false; -#endif - } - - bool close(void) - { - bool result = true; - if (initialised()) - { - if (shutdown(m_socket,SHUT_RDWR) == SOCKET_ERROR) - { - set_error(ERRNO); - result = false; - } - if (CLOSE(m_socket) == SOCKET_ERROR) - { - set_error(ERRNO); - result = false; - } - } - m_socket = INVALID_SOCKET; - m_remote_address = 0; - m_remote_port = 0; - return result; - } - - //////////////////////////////////////////////////////////////////////////// - // sending/receiving - - bool send_ready(unsigned wait) - { - // determines whether the socket is ready to send by testing whether it is writable - return select(false, true, wait); - } - - bool send (std::string& data) - { - if (!initialised()) return false; - // send the data - this will never block but may not send all the data - int bytes = ::send(m_socket, data.c_str(), data.size(), 0); - if (bytes == SOCKET_ERROR) - { - set_error(ERRNO); - return false; - } - // remove the sent bytes from the data buffer so that the buffer represents the data still to be sent - data.erase(0,bytes); - return true; - } - - bool send_packet(std::string& data, unsigned long address = 0, unsigned short port = 0) - { - if (!initialised()) return false; - // if no address specified, rely on the socket having been connected (can I test this?) - // so use the standard send, otherwise use the sendto function - int bytes = 0; - if (!address) - { - bytes = ::send(m_socket, data.c_str(), data.size(), 0); - } - else - { - sockaddr saddress; - convert_address(address, port, saddress); - bytes = ::sendto(m_socket, data.c_str(), data.size(), 0, &saddress, sizeof(saddress)); - } - if (bytes == SOCKET_ERROR) - { - set_error(ERRNO); - return false; - } - // remove the sent bytes from the data buffer so that the buffer represents the data still to be sent - data.erase(0,bytes); - return true; - } - - bool receive_ready(unsigned wait) - { - // determines whether the socket is ready to receive by testing whether it is readable - return select(true, false, wait); - } - - bool receive (std::string& data) - { - if (!initialised()) return false; - // determine how much data is available to read - unsigned long bytes = 0; - if (IOCTL(m_socket, FIONREAD, &bytes) == SOCKET_ERROR) - { - set_error(ERRNO); - return false; - } - // get the data up to the amount claimed to be present - this is non-blocking - char* buffer = new char[bytes+1]; - int read = ::recv(m_socket, buffer, bytes, 0); - if (read == SOCKET_ERROR) - { - delete[] buffer; - set_error(ERRNO); - close(); - return false; - } - if (read == 0) - { - // TODO - check whether this is an appropriate conditon to close the socket - close(); - } - else - { - // this is binary data so copy the bytes including nulls - data.append(buffer,read); - } - delete[] buffer; - return true; - } - - bool receive_packet(std::string& data, unsigned long& address, unsigned short& port) - { - if (!initialised()) return false; - // determine how much data is available to read - unsigned long bytes = 0; - if (IOCTL(m_socket, FIONREAD, &bytes) == SOCKET_ERROR) - { - set_error(ERRNO); - return false; - } - // get the data up to the amount claimed to be present - this is non-blocking - // also get the sender's details - char* buffer = new char[bytes+1]; - sockaddr saddress; - SOCKLEN_T saddress_length = sizeof(saddress); - int read = ::recvfrom(m_socket, buffer, bytes, 0, &saddress, &saddress_length); - if (read == SOCKET_ERROR) - { - // UDP connection reset means that a previous sent failed to deliver cos the address was unknown - // this is NOT an error with the sending server socket, which IS still usable - int error = ERRNO; - if (error != ECONNRESET) - { - delete[] buffer; - set_error(error); - close(); - return false; - } - } - // this is binary data so copy the bytes including nulls - data.append(buffer,read); - // also retrieve the sender's details - convert_sockaddr(saddress, address, port); - delete[] buffer; - return true; - } - - bool receive_packet(std::string& data) - { - // call the above and then discard the address details - unsigned long address = 0; - unsigned short port = 0; - return receive_packet(data, address, port); - } - - //////////////////////////////////////////////////////////////////////////// - // informational - - IP_socket_type type(void) const - { - return m_type; - } - - unsigned short local_port(void) const - { - if (!initialised()) return 0; - sockaddr saddress; - SOCKLEN_T saddress_length = sizeof(saddress); - if (::getsockname(m_socket, &saddress, &saddress_length) != 0) - { - set_error(ERRNO); - return 0; - } - unsigned long address = 0; - unsigned short port = 0; - convert_sockaddr(saddress, address, port); - return port; - } - - unsigned long remote_address(void) const - { - return m_remote_address; - } - - unsigned short remote_port(void) const - { - return m_remote_port; - } - - //////////////////////////////////////////////////////////////////////////// - // error handling - - void set_error (int error, const char* message = 0) const - { - if (error != 0) - { - m_error = error; - if (message && (message[0] != 0)) - m_message = message; - else - m_message = error_string(error); - } - } - - int error(void) const - { - return m_error; - } - - void clear_error (void) const - { - m_error = 0; - m_message.erase(); - } - - std::string message(void) const - { - return m_message; - } - - }; - - //////////////////////////////////////////////////////////////////////////////// - // Socket - common code to manipulate a socket - - // create an uninitialised socket - IP_socket::IP_socket(void) : m_impl(new IP_socket_internals) - { - } - - // create an initialised socket - // - type: create either a TCP or UDP socket - if neither, creates an uninitialised socket - IP_socket::IP_socket(IP_socket_type type) : m_impl(new IP_socket_internals) - { - initialise(type); - } - - // destroy the socket, closing it if open - IP_socket::~IP_socket(void) - { - if (m_impl->decrement()) - delete m_impl; - } - - //////////////////////////////////////////////////////////////////////////// - // copying is implemented as aliasing - - IP_socket::IP_socket(const IP_socket& right) : m_impl(0) - { - // make this an alias of right - m_impl = right.m_impl; - m_impl->increment(); - } - - IP_socket& IP_socket::operator=(const IP_socket& right) - { - // make self-copy safe - if (m_impl == right.m_impl) return *this; - // first dealias the existing implementation - if (m_impl->decrement()) - delete m_impl; - // now make this an alias of right - m_impl = right.m_impl; - m_impl->increment(); - return *this; - } - - //////////////////////////////////////////////////////////////////////////// - // initialisation, connection - - // initialise the socket - // - type: create either a TCP or UDP socket - // - returns success status - bool IP_socket::initialise(IP_socket_type type) - { - return m_impl->initialise(type); - } - - // test whether this is an initialised socket - // - returns whether this is initialised - bool IP_socket::initialised(void) const - { - return m_impl->initialised(); - } - - // close, i.e. disconnect the socket - // - returns a success flag - bool IP_socket::close(void) - { - return m_impl->close(); - } - - // function for performing IP lookup (i.e. gethostbyname) - // could be standalone but making it a member means that it can use the socket's error handler - // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) - // - returns the IP address as a long integer - zero if there's an error - unsigned long IP_socket::ip_lookup(const std::string& remote_address) - { - return m_impl->ip_lookup(remote_address); - } - - // test whether a socket is ready to communicate - // - readable: test whether socket is ready to read - // - writeable: test whether a socket is ready to write - // - timeout: if socket is not ready, time to wait before giving up - in micro-seconds - 0 means don't wait - // returns false if not ready or error - use error() method to tell - true if ready - bool IP_socket::select(bool readable, bool writeable, unsigned timeout) - { - return m_impl->select(readable, writeable, timeout); - } - - // bind the socket to a port so that it can receive from specific address - typically used by a client - // - remote_address: IP number of remote server to send/receive to/from - // - local_port: port on local machine to bind to the address - // - returns success flag - bool IP_socket::bind(unsigned long remote_address, unsigned short local_port) - { - return m_impl->bind(remote_address, local_port); - } - - // bind the socket to a port so that it can receive from any address - typically used by a server - // - local_port: port on local machine to bind to the address - // - returns success flag - bool IP_socket::bind_any(unsigned short local_port) - { - return m_impl->bind_any(local_port); - } - - // initialise a socket and set this socket up to be a listening port - // - queue: length of backlog queue to manage - may be zero - // - returns success status - bool IP_socket::listen(unsigned short queue) - { - return m_impl->listen(queue); - } - - // test for a connection on the object's socket - only applicable if it has been set up as a listening port - // - returns true if a connection is ready to be accepted - bool IP_socket::accept_ready(unsigned timeout) const - { - return m_impl->accept_ready(timeout); - } - - // accept a connection on the object's socket - only applicable if it has been set up as a listening port - // - returns the connection as a new socket - IP_socket IP_socket::accept(void) - { - return m_impl->accept(); - } - - // client connect to a server - // - address: IP number already lookup up with ip_lookup - // - port: port to connect to - // - returns a success flag - bool IP_socket::connect(unsigned long address, unsigned short port) - { - return m_impl->connect(address, port); - } - - // test whether a socket is connected and ready to communicate, returns on successful connect or timeout - // - timeout: how long to wait in microseconds if not connected yet - // - returns success flag - bool IP_socket::connected(unsigned timeout) - { - return m_impl->connected(timeout); - } - - //////////////////////////////////////////////////////////////////////////// - // sending/receiving - - // test whether a socket is connected and ready to send data, returns if ready or on timeout - // - timeout: how long to wait in microseconds if not connected yet (blocking) - // - returns status - bool IP_socket::send_ready(unsigned timeout) - { - return m_impl->send_ready(timeout); - } - - // send data through the socket - if the data is long only part of it may - // be sent. The sent part is removed from the data, so the same string can - // be sent again and again until it is empty. - // - data: string containing data to be sent - any data successfully sent is removed - // - returns success flag - bool IP_socket::send (std::string& data) - { - return m_impl->send(data); - } - - // send data through a connectionless (UDP) socket - // the data will be sent as a single packet - // - packet: string containing data to be sent - any data successfully sent is removed - // - remote_address: address of the remote host to send to - optional if the socket has been connected to remote - // - remote_port: port of the remote host to send to - optional if the socket has been connected to remote - // - returns success flag - bool IP_socket::send_packet(std::string& packet, unsigned long remote_address, unsigned short remote_port) - { - return m_impl->send_packet(packet, remote_address, remote_port); - } - - // send data through a connectionless (UDP) socket - // the data will be sent as a single packet - // only works if the socket has been connected to remote - // - packet: string containing data to be sent - any data successfully sent is removed - // - returns success flag - bool IP_socket::send_packet(std::string& packet) - { - return m_impl->send_packet(packet); - } - - // test whether a socket is connected and ready to receive data, returns if ready or on timeout - // - timeout: how long to wait in microseconds if not connected yet (blocking) - // - returns status - bool IP_socket::receive_ready(unsigned timeout) - { - return m_impl->receive_ready(timeout); - } - - // receive data through a connection-based (TCP) socket - // if the data is long only part of it may be received. The received data - // is appended to the string, building it up in stages, so the same string - // can be received again and again until all information has been - // received. - // - data: string receiving data from socket - any data successfully received is appended - // - returns success flag - bool IP_socket::receive (std::string& data) - { - return m_impl->receive(data); - } - - // receive data through a connectionless (UDP) socket - // - packet: string receiving data from socket - any data successfully received is appended - // - remote_address: returns the address of the remote host received from - // - remote_port: returns the port of the remote host received from - // - returns success flag - bool IP_socket::receive_packet(std::string& packet, unsigned long& remote_address, unsigned short& remote_port) - { - return m_impl->receive_packet(packet, remote_address, remote_port); - } - - // variant of above which does not give back the address and port of the sender - // receive data through a connectionless (UDP) socket - // - packet: string receiving data from socket - any data successfully received is appended - // - returns success flag - bool IP_socket::receive_packet(std::string& packet) - { - return m_impl->receive_packet(packet); - } - - //////////////////////////////////////////////////////////////////////////// - // informational - - IP_socket_type IP_socket::type(void) const - { - return m_impl->type(); - } - - unsigned short IP_socket::local_port(void) const - { - return m_impl->local_port(); - } - - unsigned long IP_socket::remote_address(void) const - { - return m_impl->remote_address(); - } - - unsigned short IP_socket::remote_port(void) const - { - return m_impl->remote_port(); - } - - //////////////////////////////////////////////////////////////////////////// - // error handling - - void IP_socket::set_error (int error, const std::string& message) const - { - m_impl->set_error(error, message.c_str()); - } - - void IP_socket::clear_error (void) const - { - m_impl->clear_error(); - } - - int IP_socket::error(void) const - { - return m_impl->error(); - } - - std::string IP_socket::message(void) const - { - return m_impl->message(); - } - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Contains all the platform-specific socket handling used by the TCP and UDP classes + +// TODO - any conversion required to support IPv6 + +//////////////////////////////////////////////////////////////////////////////// + +#include "ip_sockets.hpp" +#include "dprintf.hpp" +#include + +#ifdef MSWINDOWS +// Windoze-specific includes +#include +#define ERRNO WSAGetLastError() +#define HERRNO WSAGetLastError() +#define IOCTL ioctlsocket +#define CLOSE closesocket +#define SHUT_RDWR SD_BOTH +#define SOCKLEN_T int +#define SEND_FLAGS 0 +#if _MSC_VER < 1600 // not defined before Visual Studio 10 +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ECONNRESET WSAECONNRESET +#endif +#else +// Generic Unix includes +// fix for older versions of Darwin? +#define _BSD_SOCKLEN_T_ int +#include +#include +#include +#include +#include +#include +#include +#include +#define INVALID_SOCKET -1 +#define ERRNO errno +#define HERRNO h_errno +#define SOCKET int +#define SOCKET_ERROR -1 +#define IOCTL ::ioctl +#define CLOSE ::close +#define SOCKLEN_T socklen_t +#define SEND_FLAGS MSG_NOSIGNAL +#ifdef SOLARIS +// Sun put some definitions in a different place +#include +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // Utilities + + // get an operating-system error message given an error code + static std::string error_string(int error) + { + std::string result = "error " + dformat("%d",error); +#ifdef MSWINDOWS + char* message = 0; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, + 0, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // "User default language" + (LPTSTR)&message, + 0,0); + if (message) + { + result = message; + LocalFree(message); + } + // the error message is for some perverse reason newline terminated - remove this + if (result[result.size()-1] == '\n') + result.erase(result.end()-1); + if (result[result.size()-1] == '\r') + result.erase(result.end()-1); +#else + char* message = strerror(error); + if (message && message[0]) + result = message; +#endif + return result; + } + + // convert address:port into a sockaddr + static void convert_address(unsigned long address, unsigned short port, sockaddr& sa) + { + sa.sa_family = AF_INET; + unsigned short network_port = htons(port); + memcpy(&sa.sa_data[0], &network_port, sizeof(network_port)); + unsigned long network_address = htonl(address); + memcpy(&sa.sa_data[2], &network_address, sizeof(network_address)); + } + +// // convert host:port into a sockaddr +// static void convert_host(hostent& host, unsigned short port, sockaddr& sa) +// { +// sa.sa_family = host.h_addrtype; +// unsigned short network_port = htons(port); +// memcpy(&sa.sa_data[0], &network_port, sizeof(network_port)); +// memcpy(&sa.sa_data[2], host.h_addr, host.h_length); +// } + + // convert sockaddr to address:port + static void convert_sockaddr(const sockaddr& sa, unsigned long& address, unsigned short& port) + { + unsigned short network_port = 0; + memcpy(&network_port, &sa.sa_data[0], sizeof(network_port)); + port = ntohs(network_port); + unsigned long network_address = 0; + memcpy(&network_address, &sa.sa_data[2], sizeof(network_address)); + address = ntohl(network_address); + } + + //////////////////////////////////////////////////////////////////////////////// + // Initialisation + // Windows requires that Winsock is initialised before use and closed after + // These routines initialise once on first use and close on the destruction of the last object using it + // on non-windows platforms, I still increment/decrement the sockets count variable for diagnostic purposes + + static int sockets_count = 0; + + static int sockets_init(void) + { + int error = 0; + if (sockets_count++ == 0) + { +#ifdef MSWINDOWS + WSAData winsock_info; + // request Winsock 2.0 or higher + error = WSAStartup(MAKEWORD(2,0),&winsock_info); +#endif + } + return error; + } + + static int sockets_close(void) + { + int error = 0; + if (--sockets_count == 0) + { +#ifdef MSWINDOWS + if (WSACleanup() == SOCKET_ERROR) + error = ERRNO; +#endif + } + return error; + } + + //////////////////////////////////////////////////////////////////////////////// + // Socket Implementation - common code to manipulate a TCP socket + + class IP_socket_internals + { + private: + IP_socket_type m_type; + SOCKET m_socket; + unsigned long m_remote_address; + unsigned short m_remote_port; + mutable int m_error; + mutable std::string m_message; + unsigned m_count; + + // disable copying of the internals + IP_socket_internals(const IP_socket_internals&); + IP_socket_internals& operator=(const IP_socket_internals&); + + public: + + //////////////////////////////////////////////////////////////////////////// + // PIMPL alias counting + + void increment(void) + { + ++m_count; + } + + bool decrement(void) + { + --m_count; + return m_count == 0; + } + + //////////////////////////////////////////////////////////////////////////// + // constructors/destructors + + // construct an invalid socket + IP_socket_internals(void) : m_type(undefined_socket_type), m_socket(INVALID_SOCKET), m_error(0), m_count(1) + { + set_error(sockets_init()); + } + + // close on destroy + ~IP_socket_internals(void) + { + close(); + set_error(sockets_close()); + } + + //////////////////////////////////////////////////////////////////////////// + // initialisation, connection + + bool initialised(void) const + { + return m_socket != INVALID_SOCKET; + } + + // attach this object to a pre-opened socket + bool set(SOCKET socket, unsigned long remote_address, unsigned short remote_port) + { + if (initialised()) close(); + clear_error(); + m_socket = socket; + m_remote_address = remote_address; + m_remote_port = remote_port; + return true; + } + + // create a raw socket attached to this object + bool initialise(IP_socket_type type) + { + if (initialised()) close(); + clear_error(); + if ((type != TCP) && (type != UDP)) + { + set_error(-1, "Illegal socket type"); + return false; + } + // create an anonymous socket + m_socket = ::socket(AF_INET, ((type == TCP) ? SOCK_STREAM : SOCK_DGRAM), 0); + if (m_socket == INVALID_SOCKET) + { + set_error(ERRNO); + close(); + return false; + } + // record the type on success only + m_type = type; + // set the socket into non-blocking mode + unsigned long nonblocking = 1; + if (IOCTL(m_socket, FIONBIO, &nonblocking) == SOCKET_ERROR) + { + set_error(ERRNO); + return false; + } + return true; + } + + // function for performing IP lookup (i.e. gethostbyname) + // could be standalone but making it a member means that it can use the socket's error handler + // - remote_address: IP name or number + // - returns the IP address as a number - zero if there's an error + unsigned long ip_lookup(const std::string& remote_address) + { + unsigned long result = 0; + // Lookup the IP address to convert it into a host record + // this DOES lookup IP address names as well (not according to MS help !!) + // TODO - convert this to use ::getaddrinfo - ::gethostbyname is deprecated + hostent* host_info = ::gethostbyname(remote_address.c_str()); + if (!host_info) + { + set_error(HERRNO); + return 0; + } + // extract the address from the host info + unsigned long network_address = 0; + memcpy(&network_address, host_info->h_addr, host_info->h_length); + result = ntohl(network_address); + return result; + } + + // tests whether a socket is ready for communication + bool select(bool readable, bool writeable, unsigned wait) + { + if (!initialised()) return false; + // set up the readable set + fd_set readable_set; + fd_set* readable_set_ptr = 0; + if (readable) + { + FD_ZERO(&readable_set); + FD_SET(m_socket,&readable_set); + readable_set_ptr = &readable_set; + } + // set up the writeable set + fd_set writeable_set; + fd_set* writeable_set_ptr = 0; + if (writeable) + { + FD_ZERO(&writeable_set); + FD_SET(m_socket,&writeable_set); + writeable_set_ptr = &writeable_set; + } + // TODO - check the error set and lookup the error? + fd_set* error_set_ptr = 0; + // set up the timout value + // Note: a null pointer implements a blocking select + // a pointer to a zero value implements a zero-wait poll + // a pointer to a positive value implements a poll with a timeout + // I currently only implement polling with timeout which may be zero - no blocking + timeval timeout; + timeval* timeout_ptr = 0; + timeout.tv_sec = wait/1000000; + timeout.tv_usec = wait%1000000; + timeout_ptr = &timeout; + // now test the socket + int select_result = ::select(m_socket+1, readable_set_ptr, writeable_set_ptr, error_set_ptr, timeout_ptr); + switch(select_result) + { + case SOCKET_ERROR: + // select failed with an error - trap the error + set_error(ERRNO); + return false; + case 0: + // timeout exceeded without a connection appearing + return false; + default: + // at least one connection is pending + // TODO - do we need to do the extra socket options checking on Posix? + // TODO - does this connect in any way to the error_set above? + return true; + } + } + + // bind the socket to a port so that it can receive from specific address + bool bind(unsigned long remote_address, unsigned short local_port) + { + if (!initialised()) return false; + // name the socket and bind it to a port - this is a requirement for a server + sockaddr server; + convert_address(INADDR_ANY, local_port, server); + if (::bind(m_socket, &server, sizeof(server)) == SOCKET_ERROR) + { + set_error(ERRNO); + close(); + return false; + } + return true; + } + + // bind the socket to a port so that it can receive from any address + bool bind_any(unsigned short local_port) + { + return bind(INADDR_ANY, local_port); + } + + // set this socket up to be a listening port + // must have been bound to a local port already + // - length of backlog queue to manage - may be zero + // - returns success status + bool listen(unsigned short queue) + { + if (!initialised()) return false; + // set the port to listen for incoming connections + if (::listen(m_socket, (int)queue) == SOCKET_ERROR) + { + set_error(ERRNO); + close(); + return false; + } + return true; + } + + // test whether there's an incoming connection on the socket + // only applicable if it has been set up as a listening port + bool accept_ready(unsigned wait) + { + // the test for a connection being ready is the same as the test for whether the socket is readable + // see documentation for select + return select(true, false, wait); + } + + // accept a connection on the socket + // only applicable if it has been set up as a listening port + // - returns socket filled in with the accepted connection's details - or with the error fields set + IP_socket accept(void) + { + if (!initialised()) return IP_socket(); + IP_socket result; + // accept the connection, at the same time getting the address of the connecting client + sockaddr saddress; + SOCKLEN_T saddress_length = sizeof(saddress); + SOCKET socket = ::accept(m_socket, &saddress, &saddress_length); + if (socket == INVALID_SOCKET) + { + // only set the result socket with an error + result.m_impl->set_error(ERRNO); + return result; + } + // extract the contents of the address + unsigned long remote_address = 0; + unsigned short remote_port = 0; + convert_sockaddr(saddress, remote_address, remote_port); + result.m_impl->set(socket, remote_address, remote_port); + return result; + } + + // client connect to a server + // - remote_address: IP number of remote address to connect to + // - remote_port: port to connect to + bool connect(unsigned long remote_address, unsigned short remote_port) + { + if (!initialised()) return false; + // fill in the connection data structure + sockaddr connect_data; + convert_address(remote_address, remote_port, connect_data); + // connect binds the socket to a local address + // if connectionless it simply sets the default remote address + // if connectioned it makes the connection + if (::connect(m_socket, &connect_data, sizeof(connect_data)) == SOCKET_ERROR) + { + // the socket is non-blocking, so connect will almost certainly fail with EINPROGRESS which is not an error + // only catch real errors + int error = ERRNO; + if (error != EINPROGRESS && error != EWOULDBLOCK) + { + set_error(error); + return false; + } + } + // extract the remote connection details for local storage + convert_sockaddr(connect_data, m_remote_address, m_remote_port); + return true; + } + + // test whether a socket is connected and ready to communicate + bool connected(unsigned wait) + { + if (!initialised()) return false; + // Linux and Windows docs say test with select for whether socket is + // writable. However, a problem has been reported with Linux whereby + // the OS will report a socket as writable when it isn't + // first use the select method + if (!select(false, true, wait)) + return false; +#ifdef MSWINDOWS + // Windows needs no further processing - select method works + return true; +#else + // Posix version needs further checking using the socket options + // DJDM: socket has returned EINPROGRESS on the first attempt at connection + // it has also returned that it can be written to + // we must now ask it if it has actually connected - using getsockopt + int error = 0; + socklen_t serror = sizeof(int); + if (::getsockopt(m_socket, SOL_SOCKET, SO_ERROR, &error, &serror)==0) + // handle the error value - one of them means that the socket has connected + if (!error || error == EISCONN) + return true; + return false; +#endif + } + + bool close(void) + { + bool result = true; + if (initialised()) + { + if (shutdown(m_socket,SHUT_RDWR) == SOCKET_ERROR) + { + set_error(ERRNO); + result = false; + } + if (CLOSE(m_socket) == SOCKET_ERROR) + { + set_error(ERRNO); + result = false; + } + } + m_socket = INVALID_SOCKET; + m_remote_address = 0; + m_remote_port = 0; + return result; + } + + //////////////////////////////////////////////////////////////////////////// + // sending/receiving + + bool send_ready(unsigned wait) + { + // determines whether the socket is ready to send by testing whether it is writable + return select(false, true, wait); + } + + bool send (std::string& data) + { + if (!initialised()) return false; + // send the data - this will never block but may not send all the data + int bytes = ::send(m_socket, data.c_str(), data.size(), SEND_FLAGS); + if (bytes == SOCKET_ERROR) + { + set_error(ERRNO); + return false; + } + // remove the sent bytes from the data buffer so that the buffer represents the data still to be sent + data.erase(0,bytes); + return true; + } + + bool send_packet(std::string& data, unsigned long address = 0, unsigned short port = 0) + { + if (!initialised()) return false; + // if no address specified, rely on the socket having been connected (can I test this?) + // so use the standard send, otherwise use the sendto function + int bytes = 0; + if (!address) + { + bytes = ::send(m_socket, data.c_str(), data.size(), SEND_FLAGS); + } + else + { + sockaddr saddress; + convert_address(address, port, saddress); + bytes = ::sendto(m_socket, data.c_str(), data.size(), SEND_FLAGS, &saddress, sizeof(saddress)); + } + if (bytes == SOCKET_ERROR) + { + set_error(ERRNO); + return false; + } + // remove the sent bytes from the data buffer so that the buffer represents the data still to be sent + data.erase(0,bytes); + return true; + } + + bool receive_ready(unsigned wait) + { + // determines whether the socket is ready to receive by testing whether it is readable + return select(true, false, wait); + } + + bool receive (std::string& data) + { + if (!initialised()) return false; + // determine how much data is available to read + unsigned long bytes = 0; + if (IOCTL(m_socket, FIONREAD, &bytes) == SOCKET_ERROR) + { + set_error(ERRNO); + return false; + } + // get the data up to the amount claimed to be present - this is non-blocking + char* buffer = new char[bytes+1]; + int read = ::recv(m_socket, buffer, bytes, 0); + if (read == SOCKET_ERROR) + { + delete[] buffer; + set_error(ERRNO); + close(); + return false; + } + if (read == 0) + { + // TODO - check whether this is an appropriate conditon to close the socket + close(); + } + else + { + // this is binary data so copy the bytes including nulls + data.append(buffer,read); + } + delete[] buffer; + return true; + } + + bool receive_packet(std::string& data, unsigned long& address, unsigned short& port) + { + if (!initialised()) return false; + // determine how much data is available to read + unsigned long bytes = 0; + if (IOCTL(m_socket, FIONREAD, &bytes) == SOCKET_ERROR) + { + set_error(ERRNO); + return false; + } + // get the data up to the amount claimed to be present - this is non-blocking + // also get the sender's details + char* buffer = new char[bytes+1]; + sockaddr saddress; + SOCKLEN_T saddress_length = sizeof(saddress); + int read = ::recvfrom(m_socket, buffer, bytes, 0, &saddress, &saddress_length); + if (read == SOCKET_ERROR) + { + // UDP connection reset means that a previous sent failed to deliver cos the address was unknown + // this is NOT an error with the sending server socket, which IS still usable + int error = ERRNO; + if (error != ECONNRESET) + { + delete[] buffer; + set_error(error); + close(); + return false; + } + } + // this is binary data so copy the bytes including nulls + data.append(buffer,read); + // also retrieve the sender's details + convert_sockaddr(saddress, address, port); + delete[] buffer; + return true; + } + + bool receive_packet(std::string& data) + { + // call the above and then discard the address details + unsigned long address = 0; + unsigned short port = 0; + return receive_packet(data, address, port); + } + + //////////////////////////////////////////////////////////////////////////// + // informational + + IP_socket_type type(void) const + { + return m_type; + } + + unsigned short local_port(void) const + { + if (!initialised()) return 0; + sockaddr saddress; + SOCKLEN_T saddress_length = sizeof(saddress); + if (::getsockname(m_socket, &saddress, &saddress_length) != 0) + { + set_error(ERRNO); + return 0; + } + unsigned long address = 0; + unsigned short port = 0; + convert_sockaddr(saddress, address, port); + return port; + } + + unsigned long remote_address(void) const + { + return m_remote_address; + } + + unsigned short remote_port(void) const + { + return m_remote_port; + } + + //////////////////////////////////////////////////////////////////////////// + // error handling + + void set_error (int error, const char* message = 0) const + { + if (error != 0) + { + m_error = error; + if (message && (message[0] != 0)) + m_message = message; + else + m_message = error_string(error); + } + } + + int error(void) const + { + return m_error; + } + + void clear_error (void) const + { + m_error = 0; + m_message.erase(); + } + + std::string message(void) const + { + return m_message; + } + + }; + + //////////////////////////////////////////////////////////////////////////////// + // Socket - common code to manipulate a socket + + // create an uninitialised socket + IP_socket::IP_socket(void) : m_impl(new IP_socket_internals) + { + } + + // create an initialised socket + // - type: create either a TCP or UDP socket - if neither, creates an uninitialised socket + IP_socket::IP_socket(IP_socket_type type) : m_impl(new IP_socket_internals) + { + initialise(type); + } + + // destroy the socket, closing it if open + IP_socket::~IP_socket(void) + { + if (m_impl->decrement()) + delete m_impl; + } + + //////////////////////////////////////////////////////////////////////////// + // copying is implemented as aliasing + + IP_socket::IP_socket(const IP_socket& right) : m_impl(0) + { + // make this an alias of right + m_impl = right.m_impl; + m_impl->increment(); + } + + IP_socket& IP_socket::operator=(const IP_socket& right) + { + // make self-copy safe + if (m_impl == right.m_impl) return *this; + // first dealias the existing implementation + if (m_impl->decrement()) + delete m_impl; + // now make this an alias of right + m_impl = right.m_impl; + m_impl->increment(); + return *this; + } + + //////////////////////////////////////////////////////////////////////////// + // initialisation, connection + + // initialise the socket + // - type: create either a TCP or UDP socket + // - returns success status + bool IP_socket::initialise(IP_socket_type type) + { + return m_impl->initialise(type); + } + + // test whether this is an initialised socket + // - returns whether this is initialised + bool IP_socket::initialised(void) const + { + return m_impl->initialised(); + } + + // close, i.e. disconnect the socket + // - returns a success flag + bool IP_socket::close(void) + { + return m_impl->close(); + } + + // function for performing IP lookup (i.e. gethostbyname) + // could be standalone but making it a member means that it can use the socket's error handler + // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) + // - returns the IP address as a long integer - zero if there's an error + unsigned long IP_socket::ip_lookup(const std::string& remote_address) + { + return m_impl->ip_lookup(remote_address); + } + + // test whether a socket is ready to communicate + // - readable: test whether socket is ready to read + // - writeable: test whether a socket is ready to write + // - timeout: if socket is not ready, time to wait before giving up - in micro-seconds - 0 means don't wait + // returns false if not ready or error - use error() method to tell - true if ready + bool IP_socket::select(bool readable, bool writeable, unsigned timeout) + { + return m_impl->select(readable, writeable, timeout); + } + + // bind the socket to a port so that it can receive from specific address - typically used by a client + // - remote_address: IP number of remote server to send/receive to/from + // - local_port: port on local machine to bind to the address + // - returns success flag + bool IP_socket::bind(unsigned long remote_address, unsigned short local_port) + { + return m_impl->bind(remote_address, local_port); + } + + // bind the socket to a port so that it can receive from any address - typically used by a server + // - local_port: port on local machine to bind to the address + // - returns success flag + bool IP_socket::bind_any(unsigned short local_port) + { + return m_impl->bind_any(local_port); + } + + // initialise a socket and set this socket up to be a listening port + // - queue: length of backlog queue to manage - may be zero + // - returns success status + bool IP_socket::listen(unsigned short queue) + { + return m_impl->listen(queue); + } + + // test for a connection on the object's socket - only applicable if it has been set up as a listening port + // - returns true if a connection is ready to be accepted + bool IP_socket::accept_ready(unsigned timeout) const + { + return m_impl->accept_ready(timeout); + } + + // accept a connection on the object's socket - only applicable if it has been set up as a listening port + // - returns the connection as a new socket + IP_socket IP_socket::accept(void) + { + return m_impl->accept(); + } + + // client connect to a server + // - address: IP number already lookup up with ip_lookup + // - port: port to connect to + // - returns a success flag + bool IP_socket::connect(unsigned long address, unsigned short port) + { + return m_impl->connect(address, port); + } + + // test whether a socket is connected and ready to communicate, returns on successful connect or timeout + // - timeout: how long to wait in microseconds if not connected yet + // - returns success flag + bool IP_socket::connected(unsigned timeout) + { + return m_impl->connected(timeout); + } + + //////////////////////////////////////////////////////////////////////////// + // sending/receiving + + // test whether a socket is connected and ready to send data, returns if ready or on timeout + // - timeout: how long to wait in microseconds if not connected yet (blocking) + // - returns status + bool IP_socket::send_ready(unsigned timeout) + { + return m_impl->send_ready(timeout); + } + + // send data through the socket - if the data is long only part of it may + // be sent. The sent part is removed from the data, so the same string can + // be sent again and again until it is empty. + // - data: string containing data to be sent - any data successfully sent is removed + // - returns success flag + bool IP_socket::send (std::string& data) + { + return m_impl->send(data); + } + + // send data through a connectionless (UDP) socket + // the data will be sent as a single packet + // - packet: string containing data to be sent - any data successfully sent is removed + // - remote_address: address of the remote host to send to - optional if the socket has been connected to remote + // - remote_port: port of the remote host to send to - optional if the socket has been connected to remote + // - returns success flag + bool IP_socket::send_packet(std::string& packet, unsigned long remote_address, unsigned short remote_port) + { + return m_impl->send_packet(packet, remote_address, remote_port); + } + + // send data through a connectionless (UDP) socket + // the data will be sent as a single packet + // only works if the socket has been connected to remote + // - packet: string containing data to be sent - any data successfully sent is removed + // - returns success flag + bool IP_socket::send_packet(std::string& packet) + { + return m_impl->send_packet(packet); + } + + // test whether a socket is connected and ready to receive data, returns if ready or on timeout + // - timeout: how long to wait in microseconds if not connected yet (blocking) + // - returns status + bool IP_socket::receive_ready(unsigned timeout) + { + return m_impl->receive_ready(timeout); + } + + // receive data through a connection-based (TCP) socket + // if the data is long only part of it may be received. The received data + // is appended to the string, building it up in stages, so the same string + // can be received again and again until all information has been + // received. + // - data: string receiving data from socket - any data successfully received is appended + // - returns success flag + bool IP_socket::receive (std::string& data) + { + return m_impl->receive(data); + } + + // receive data through a connectionless (UDP) socket + // - packet: string receiving data from socket - any data successfully received is appended + // - remote_address: returns the address of the remote host received from + // - remote_port: returns the port of the remote host received from + // - returns success flag + bool IP_socket::receive_packet(std::string& packet, unsigned long& remote_address, unsigned short& remote_port) + { + return m_impl->receive_packet(packet, remote_address, remote_port); + } + + // variant of above which does not give back the address and port of the sender + // receive data through a connectionless (UDP) socket + // - packet: string receiving data from socket - any data successfully received is appended + // - returns success flag + bool IP_socket::receive_packet(std::string& packet) + { + return m_impl->receive_packet(packet); + } + + //////////////////////////////////////////////////////////////////////////// + // informational + + IP_socket_type IP_socket::type(void) const + { + return m_impl->type(); + } + + unsigned short IP_socket::local_port(void) const + { + return m_impl->local_port(); + } + + unsigned long IP_socket::remote_address(void) const + { + return m_impl->remote_address(); + } + + unsigned short IP_socket::remote_port(void) const + { + return m_impl->remote_port(); + } + + //////////////////////////////////////////////////////////////////////////// + // error handling + + void IP_socket::set_error (int error, const std::string& message) const + { + m_impl->set_error(error, message.c_str()); + } + + void IP_socket::clear_error (void) const + { + m_impl->clear_error(); + } + + int IP_socket::error(void) const + { + return m_impl->error(); + } + + std::string IP_socket::message(void) const + { + return m_impl->message(); + } + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus diff --git a/src/stlplus/portability/ip_sockets.hpp b/src/stlplus/portability/ip_sockets.hpp index f0bd314..df0533b 100644 --- a/src/stlplus/portability/ip_sockets.hpp +++ b/src/stlplus/portability/ip_sockets.hpp @@ -1,233 +1,233 @@ -#ifndef STLPLUS_IP_SOCKET -#define STLPLUS_IP_SOCKET -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// A platform-independent (Unix and Windows anyway) interface to Internet-Protocol sockets - -//////////////////////////////////////////////////////////////////////////////// -#include "portability_fixes.hpp" -#include - -namespace stlplus -{ - - ////////////////////////////////////////////////////////////////////////////// - // internals - // use a PIMPL interface to hide the platform-specifics in the implementation - - class IP_socket_internals; - - //////////////////////////////////////////////////////////////////////////// - // Types of socket supported - - enum IP_socket_type {undefined_socket_type = -1, TCP = 0, UDP = 1}; - - ////////////////////////////////////////////////////////////////////////////// - // Socket class - - class IP_socket - { - public: - - //////////////////////////////////////////////////////////////////////////// - // constructors/destructors - - // create an uninitialised socket - IP_socket(void); - - // create an initialised socket - // - type: create either a TCP or UDP socket - IP_socket(IP_socket_type type); - - // destroy the socket, closing it if open - ~IP_socket(void); - - // copying is implemented as aliasing - IP_socket(const IP_socket&); - IP_socket& operator=(const IP_socket&); - - //////////////////////////////////////////////////////////////////////////// - // initialisation - - // initialise the socket - // - type: create either a TCP or UDP socket - // - returns success status - bool initialise(IP_socket_type type); - - // test whether this is an initialised socket - // - returns whether this is initialised - bool initialised(void) const; - - // close, i.e. disconnect the socket - // - returns a success flag - bool close(void); - - ////////////////////////////////////////////////////////////////////////////// - // Socket configuration - - // function for performing IP lookup (i.e. gethostbyname) - // could be standalone but making it a member means that it can use the socket's error handler - // i.e. if this fails, the sockets error code will be set - clear it to use the socket again - // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) - // - returns the IP address as a long integer - zero if there's an error - unsigned long ip_lookup(const std::string& remote_address); - - // test whether a socket is ready to communicate - // - readable: test whether socket is ready to read - // - writeable: test whether a socket is ready to write - // - timeout: if socket is not ready, time to wait before giving up - in micro-seconds - 0 means don't wait - // returns false if not ready or error - use error() method to tell - true if ready - bool select(bool readable, bool writeable, unsigned timeout = 0); - - // bind the socket to a port so that it can receive from specific address - typically used by a client - // - remote_address: IP number of remote server to send/receive to/from - // - local_port: port on local machine to bind to the address - // - returns success flag - bool bind(unsigned long remote_address, unsigned short local_port); - - // bind the socket to a port so that it can receive from any address - typically used by a server - // - local_port: port on local machine to bind to the address - // - returns success flag - bool bind_any(unsigned short local_port); - - // set this socket up to be a listening port - // socket must be bound to a port already - // - queue: length of backlog queue to manage - may be zero meaning no queue - // - returns success status - bool listen(unsigned short queue = 0); - - // test for a connection on the socket - // only applicable if it has been set up as a listening port - // - timeout: how long to wait in microseconds if not connected yet - // - returns true if a connection is ready to be accepted - bool accept_ready(unsigned timeout = 0) const; - - // accept a connection on the socket - // only applicable if it has been set up as a listening port - // - returns the connection as a new socket - IP_socket accept(void); - - // create a connection - usually used by a client - // TCP: client connect to a remote server - // UDP: setup remote address and port for sends - // - remote_address: IP number already looked up using ip_lookup - // - remote_port: port to connect to - // - returns a success flag - bool connect(unsigned long remote_address, unsigned short remote_port); - - // test whether a socket is connected and ready to communicate, returns on successful connect or timeout - // - timeout: how long to wait in microseconds if not connected yet - // - returns true if connected and ready to communicate, false if not ready or error - bool connected(unsigned timeout = 0); - - //////////////////////////////////////////////////////////////////////////// - // sending/receiving - - // test whether a socket is connected and ready to send data, returns if ready or on timeout - // - timeout: how long to wait in microseconds if not connected yet (blocking) - // - returns status - bool send_ready(unsigned timeout = 0); - - // send data through a connection-based (TCP) socket - // if the data is long only part of it may be sent. The sent part is - // removed from the data, so the same string can be sent again and again - // until it is empty. - // - data: string containing data to be sent - any data successfully sent is removed - // - returns success flag - bool send(std::string& data); - - // send data through a connectionless (UDP) socket - // the data will be sent as a single packet - // - packet: string containing data to be sent - any data successfully sent is removed - // - remote_address: address of the remote host to send to - optional if the socket has been connected to remote - // - remote_port: port of the remote host to send to - optional if the socket has been connected to remote - // - returns success flag - bool send_packet(std::string& packet, unsigned long remote_address, unsigned short remote_port); - - // send data through a connectionless (UDP) socket - // the data will be sent as a single packet - // only works if the socket has been connected to remote - // - packet: string containing data to be sent - any data successfully sent is removed - // - returns success flag - bool send_packet(std::string& packet); - - // test whether a socket is connected and ready to receive data, returns if ready or on timeout - // - timeout: how long to wait in microseconds if not connected yet (blocking) - // - returns status - bool receive_ready(unsigned wait = 0); - - // receive data through a connection-based (TCP) socket - // if the data is long only part of it may be received. The received data - // is appended to the string, building it up in stages, so the same string - // can be received again and again until all information has been - // received. - // - data: string receiving data from socket - any data successfully received is appended - // - returns success flag - bool receive(std::string& data); - - // receive data through a connectionless (UDP) socket - // - packet: string receiving data from socket - any data successfully received is appended - // - remote_address: returns the address of the remote host received from - // - remote_port: returns the port of the remote host received from - // - returns success flag - bool receive_packet(std::string& packet, unsigned long& remote_address, unsigned short& remote_port); - - // variant of above which does not give back the address and port of the sender - // receive data through a connectionless (UDP) socket - // - packet: string receiving data from socket - any data successfully received is appended - // - returns success flag - bool receive_packet(std::string& packet); - - //////////////////////////////////////////////////////////////////////////// - // informational - - // gets the type of socket - // - returns undefined_socket_type, TCP or UDP - IP_socket_type type(void) const; - - // the local port number of the connection - // returns the port number, 0 if not bound to a port - unsigned short local_port(void) const; - - // the remote address of the connection - // returns the address, 0 if not connected - unsigned long remote_address(void) const; - - // the remote port number of the connection - // returns the port number, 0 if not connected to a port - unsigned short remote_port(void) const; - - //////////////////////////////////////////////////////////////////////////// - // error handling - // errors are set internally - // an error code of 0 is the test for no error, don't rely on the message being an empty string - // an error code != 0 means an error, then there will be a message explaining the error - - // indicate an error - mostly used internally, you can set your own errors - use a negative code - void set_error (int error, const std::string& message) const; - - // if an error is set it stays set - so you must clear it before further operations - void clear_error (void) const; - - // get the error code of the last error - int error(void) const; - - // get the explanatory message of the last error - std::string message(void) const; - - //////////////////////////////////////////////////////////////////////////// - - private: - friend class IP_socket_internals; - IP_socket_internals* m_impl; - }; - - -} // end namespace stlplus - -#endif +#ifndef STLPLUS_IP_SOCKET +#define STLPLUS_IP_SOCKET +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// A platform-independent (Unix and Windows anyway) interface to Internet-Protocol sockets + +//////////////////////////////////////////////////////////////////////////////// +#include "portability_fixes.hpp" +#include + +namespace stlplus +{ + + ////////////////////////////////////////////////////////////////////////////// + // internals + // use a PIMPL interface to hide the platform-specifics in the implementation + + class IP_socket_internals; + + //////////////////////////////////////////////////////////////////////////// + // Types of socket supported + + enum IP_socket_type {undefined_socket_type = -1, TCP = 0, UDP = 1}; + + ////////////////////////////////////////////////////////////////////////////// + // Socket class + + class IP_socket + { + public: + + //////////////////////////////////////////////////////////////////////////// + // constructors/destructors + + // create an uninitialised socket + IP_socket(void); + + // create an initialised socket + // - type: create either a TCP or UDP socket + IP_socket(IP_socket_type type); + + // destroy the socket, closing it if open + ~IP_socket(void); + + // copying is implemented as aliasing + IP_socket(const IP_socket&); + IP_socket& operator=(const IP_socket&); + + //////////////////////////////////////////////////////////////////////////// + // initialisation + + // initialise the socket + // - type: create either a TCP or UDP socket + // - returns success status + bool initialise(IP_socket_type type); + + // test whether this is an initialised socket + // - returns whether this is initialised + bool initialised(void) const; + + // close, i.e. disconnect the socket + // - returns a success flag + bool close(void); + + ////////////////////////////////////////////////////////////////////////////// + // Socket configuration + + // function for performing IP lookup (i.e. gethostbyname) + // could be standalone but making it a member means that it can use the socket's error handler + // i.e. if this fails, the sockets error code will be set - clear it to use the socket again + // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) + // - returns the IP address as a long integer - zero if there's an error + unsigned long ip_lookup(const std::string& remote_address); + + // test whether a socket is ready to communicate + // - readable: test whether socket is ready to read + // - writeable: test whether a socket is ready to write + // - timeout: if socket is not ready, time to wait before giving up - in micro-seconds - 0 means don't wait + // returns false if not ready or error - use error() method to tell - true if ready + bool select(bool readable, bool writeable, unsigned timeout = 0); + + // bind the socket to a port so that it can receive from specific address - typically used by a client + // - remote_address: IP number of remote server to send/receive to/from + // - local_port: port on local machine to bind to the address + // - returns success flag + bool bind(unsigned long remote_address, unsigned short local_port); + + // bind the socket to a port so that it can receive from any address - typically used by a server + // - local_port: port on local machine to bind to the address + // - returns success flag + bool bind_any(unsigned short local_port); + + // set this socket up to be a listening port + // socket must be bound to a port already + // - queue: length of backlog queue to manage - may be zero meaning no queue + // - returns success status + bool listen(unsigned short queue = 0); + + // test for a connection on the socket + // only applicable if it has been set up as a listening port + // - timeout: how long to wait in microseconds if not connected yet + // - returns true if a connection is ready to be accepted + bool accept_ready(unsigned timeout = 0) const; + + // accept a connection on the socket + // only applicable if it has been set up as a listening port + // - returns the connection as a new socket + IP_socket accept(void); + + // create a connection - usually used by a client + // TCP: client connect to a remote server + // UDP: setup remote address and port for sends + // - remote_address: IP number already looked up using ip_lookup + // - remote_port: port to connect to + // - returns a success flag + bool connect(unsigned long remote_address, unsigned short remote_port); + + // test whether a socket is connected and ready to communicate, returns on successful connect or timeout + // - timeout: how long to wait in microseconds if not connected yet + // - returns true if connected and ready to communicate, false if not ready or error + bool connected(unsigned timeout = 0); + + //////////////////////////////////////////////////////////////////////////// + // sending/receiving + + // test whether a socket is connected and ready to send data, returns if ready or on timeout + // - timeout: how long to wait in microseconds if not connected yet (blocking) + // - returns status + bool send_ready(unsigned timeout = 0); + + // send data through a connection-based (TCP) socket + // if the data is long only part of it may be sent. The sent part is + // removed from the data, so the same string can be sent again and again + // until it is empty. + // - data: string containing data to be sent - any data successfully sent is removed + // - returns success flag + bool send(std::string& data); + + // send data through a connectionless (UDP) socket + // the data will be sent as a single packet + // - packet: string containing data to be sent - any data successfully sent is removed + // - remote_address: address of the remote host to send to - optional if the socket has been connected to remote + // - remote_port: port of the remote host to send to - optional if the socket has been connected to remote + // - returns success flag + bool send_packet(std::string& packet, unsigned long remote_address, unsigned short remote_port); + + // send data through a connectionless (UDP) socket + // the data will be sent as a single packet + // only works if the socket has been connected to remote + // - packet: string containing data to be sent - any data successfully sent is removed + // - returns success flag + bool send_packet(std::string& packet); + + // test whether a socket is connected and ready to receive data, returns if ready or on timeout + // - timeout: how long to wait in microseconds if not connected yet (blocking) + // - returns status + bool receive_ready(unsigned wait = 0); + + // receive data through a connection-based (TCP) socket + // if the data is long only part of it may be received. The received data + // is appended to the string, building it up in stages, so the same string + // can be received again and again until all information has been + // received. + // - data: string receiving data from socket - any data successfully received is appended + // - returns success flag + bool receive(std::string& data); + + // receive data through a connectionless (UDP) socket + // - packet: string receiving data from socket - any data successfully received is appended + // - remote_address: returns the address of the remote host received from + // - remote_port: returns the port of the remote host received from + // - returns success flag + bool receive_packet(std::string& packet, unsigned long& remote_address, unsigned short& remote_port); + + // variant of above which does not give back the address and port of the sender + // receive data through a connectionless (UDP) socket + // - packet: string receiving data from socket - any data successfully received is appended + // - returns success flag + bool receive_packet(std::string& packet); + + //////////////////////////////////////////////////////////////////////////// + // informational + + // gets the type of socket + // - returns undefined_socket_type, TCP or UDP + IP_socket_type type(void) const; + + // the local port number of the connection + // returns the port number, 0 if not bound to a port + unsigned short local_port(void) const; + + // the remote address of the connection + // returns the address, 0 if not connected + unsigned long remote_address(void) const; + + // the remote port number of the connection + // returns the port number, 0 if not connected to a port + unsigned short remote_port(void) const; + + //////////////////////////////////////////////////////////////////////////// + // error handling + // errors are set internally + // an error code of 0 is the test for no error, don't rely on the message being an empty string + // an error code != 0 means an error, then there will be a message explaining the error + + // indicate an error - mostly used internally, you can set your own errors - use a negative code + void set_error (int error, const std::string& message) const; + + // if an error is set it stays set - so you must clear it before further operations + void clear_error (void) const; + + // get the error code of the last error + int error(void) const; + + // get the explanatory message of the last error + std::string message(void) const; + + //////////////////////////////////////////////////////////////////////////// + + private: + friend class IP_socket_internals; + IP_socket_internals* m_impl; + }; + + +} // end namespace stlplus + +#endif diff --git a/src/stlplus/portability/portability.hpp b/src/stlplus/portability/portability.hpp index 92ec10a..c793974 100644 --- a/src/stlplus/portability/portability.hpp +++ b/src/stlplus/portability/portability.hpp @@ -1,28 +1,28 @@ -#ifndef STLPLUS_PORTABILITY -#define STLPLUS_PORTABILITY -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Allows all the STLplus portability packages to be included in one go - -//////////////////////////////////////////////////////////////////////////////// - -#include "build.hpp" -#include "debug.hpp" -#include "dprintf.hpp" -#include "dynaload.hpp" -#include "file_system.hpp" -#include "inf.hpp" -#include "subprocesses.hpp" -#include "tcp_sockets.hpp" -#include "udp_sockets.hpp" -#include "time.hpp" -#include "version.hpp" -#include "wildcard.hpp" - -//////////////////////////////////////////////////////////////////////////////// -#endif +#ifndef STLPLUS_PORTABILITY +#define STLPLUS_PORTABILITY +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Allows all the STLplus portability packages to be included in one go + +//////////////////////////////////////////////////////////////////////////////// + +#include "build.hpp" +#include "debug.hpp" +#include "dprintf.hpp" +#include "dynaload.hpp" +#include "file_system.hpp" +#include "inf.hpp" +#include "subprocesses.hpp" +#include "tcp_sockets.hpp" +#include "udp_sockets.hpp" +#include "time.hpp" +#include "version.hpp" +#include "wildcard.hpp" + +//////////////////////////////////////////////////////////////////////////////// +#endif diff --git a/src/stlplus/portability/portability_exceptions.hpp b/src/stlplus/portability/portability_exceptions.hpp index e47e6a0..1579224 100644 --- a/src/stlplus/portability/portability_exceptions.hpp +++ b/src/stlplus/portability/portability_exceptions.hpp @@ -1,33 +1,33 @@ -#ifndef STLPLUS_PORTABILITY_EXCEPTIONS -#define STLPLUS_PORTABILITY_EXCEPTIONS -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Adds missing arithmetic exceptions used in this library but missing from std - -//////////////////////////////////////////////////////////////////////////////// -#include "portability_fixes.hpp" -#include -#include - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // thrown by division when the divisor is zero - // This is a subclass of std::logic_error so can be caught by a generic catch clause for the superclass - - class divide_by_zero : public std::logic_error { - public: - divide_by_zero (const std::string& what_arg): std::logic_error (what_arg) { } - }; - -//////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#endif +#ifndef STLPLUS_PORTABILITY_EXCEPTIONS +#define STLPLUS_PORTABILITY_EXCEPTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Adds missing arithmetic exceptions used in this library but missing from std + +//////////////////////////////////////////////////////////////////////////////// +#include "portability_fixes.hpp" +#include +#include + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // thrown by division when the divisor is zero + // This is a subclass of std::logic_error so can be caught by a generic catch clause for the superclass + + class divide_by_zero : public std::logic_error { + public: + divide_by_zero (const std::string& what_arg): std::logic_error (what_arg) { } + }; + +//////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#endif diff --git a/src/stlplus/portability/portability_fixes.cpp b/src/stlplus/portability/portability_fixes.cpp index 5b49e0c..3234477 100644 --- a/src/stlplus/portability/portability_fixes.cpp +++ b/src/stlplus/portability/portability_fixes.cpp @@ -1,39 +1,39 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// -#include "portability_fixes.hpp" - -#ifdef MSWINDOWS -#include "windows.h" -#endif - -//////////////////////////////////////////////////////////////////////////////// -// problems with missing functions -//////////////////////////////////////////////////////////////////////////////// - -#ifdef MSWINDOWS -unsigned sleep(unsigned seconds) -{ - Sleep(1000*seconds); - // should return remaining time if interrupted - however Windoze Sleep cannot be interrupted - return 0; -} -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Function for establishing endian-ness -//////////////////////////////////////////////////////////////////////////////// - -bool stlplus::little_endian(void) -{ - int sample = 1; - char* sample_bytes = (char*)&sample; - return sample_bytes[0] != 0; -} - -//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// +#include "portability_fixes.hpp" + +#ifdef MSWINDOWS +#include "windows.h" +#endif + +//////////////////////////////////////////////////////////////////////////////// +// problems with missing functions +//////////////////////////////////////////////////////////////////////////////// + +#ifdef MSWINDOWS +unsigned sleep(unsigned seconds) +{ + Sleep(1000*seconds); + // should return remaining time if interrupted - however Windoze Sleep cannot be interrupted + return 0; +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Function for establishing endian-ness +//////////////////////////////////////////////////////////////////////////////// + +bool stlplus::little_endian(void) +{ + int sample = 1; + char* sample_bytes = (char*)&sample; + return sample_bytes[0] != 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/stlplus/portability/portability_fixes.hpp b/src/stlplus/portability/portability_fixes.hpp index 26c426a..78d5c75 100644 --- a/src/stlplus/portability/portability_fixes.hpp +++ b/src/stlplus/portability/portability_fixes.hpp @@ -1,127 +1,127 @@ -#ifndef STLPLUS_PORTABILITY_FIXES -#define STLPLUS_PORTABILITY_FIXES -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Contains work arounds for OS or Compiler specific problems to try to make -// them look more alike - -// It is strongly recommended that this header be included as the first -// #include in every source file - -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Problem with MicroSoft defining two different macros to identify Windows -//////////////////////////////////////////////////////////////////////////////// - -#if defined(_WIN32) || defined(_WIN32_WCE) -#define MSWINDOWS -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Problems with unnecessary or unfixable compiler warnings -//////////////////////////////////////////////////////////////////////////////// - -#ifdef _MSC_VER -// Microsoft Visual Studio -// shut up the following irritating warnings -// 4786 - VC6, identifier string exceeded maximum allowable length and was truncated (only affects debugger) -// 4305 - VC6, identifier type was converted to a smaller type -// 4503 - VC6, decorated name was longer than the maximum the compiler allows (only affects debugger) -// 4309 - VC6, type conversion operation caused a constant to exceeded the space allocated for it -// 4290 - VC6, C++ exception specification ignored -// 4800 - VC6, forcing value to bool 'true' or 'false' (performance warning) -// 4675 - VC7.1, "change" in function overload resolution _might_ have altered program -// 4996 - VC8, 'xxxx' was declared deprecated -#pragma warning(disable: 4786 4305 4503 4309 4290 4800 4675 4996) -#endif - -#ifdef __BORLANDC__ -// Borland -// Shut up the following irritating warnings -// 8026 - Functions with exception specifications are not expanded inline -// 8027 - Functions with xxx are not expanded inline -#pragma warn -8026 -#pragma warn -8027 -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Problems with redefinition of min/max in various different versions of library headers -//////////////////////////////////////////////////////////////////////////////// - -// The Windows headers define macros called max/min which conflict with the templates std::max and std::min. -// So, to avoid conflicts, MS removed the std::max/min rather than fixing the problem! -// From Visual Studio .NET (SV7, compiler version 13.00) the STL templates have been added correctly. -// For MFC compatibility, only undef min and max in non-MFC programs - some bits of MFC -// use macro min/max in headers. - -// I've created extra template function definitions minimum/maximum that avoid all the problems above - -namespace stlplus -{ - template const T& maximum(const T& l, const T& r) {return l > r ? l : r;} - template const T& minimum(const T& l, const T& r) {return l < r ? l : r;} -} - -//////////////////////////////////////////////////////////////////////////////// -// Problems with differences between namespaces -//////////////////////////////////////////////////////////////////////////////// - -// Note: not sure of the relevance of this - maybe deprecated? -// problem in gcc pre-v3 where the sub-namespaces in std aren't present -// this mean that the statement "using namespace std::rel_ops" created an error because the namespace didn't exist - -// I've done a fix here that creates an empty namespace for this case, but I -// do *not* try to move the contents of std::rel_ops into namespace std -// This fix only works if you use "using namespace std::rel_ops" to bring in the template relational operators (e.g. != defined i.t.o. ==) - -#ifdef __GNUC__ -namespace std -{ - namespace rel_ops - { - } -} -#endif - -//////////////////////////////////////////////////////////////////////////////// -// problems with missing functions -//////////////////////////////////////////////////////////////////////////////// - -#ifdef MSWINDOWS -unsigned sleep(unsigned seconds); -#else -#include -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Function for establishing endian-ness -//////////////////////////////////////////////////////////////////////////////// -// Different machine architectures store data using different byte orders. -// This is referred to as Big- and Little-Endian Byte Ordering. -// -// The issue is: where does a pointer to an integer type actually point? -// -// In both conventions, the address points to the left of the word but: -// Big-Endian - The most significant byte is on the left end of a word -// Little-Endian - The least significant byte is on the left end of a word -// -// Bytes are addressed left to right, so in big-endian order byte 0 is the -// msB, whereas in little-endian order byte 0 is the lsB. For example, -// Intel-based machines store data in little-endian byte order so byte 0 is -// the lsB. -// -// This function establishes byte order at run-time - -namespace stlplus -{ - bool little_endian(void); -} - -//////////////////////////////////////////////////////////////////////////////// -#endif +#ifndef STLPLUS_PORTABILITY_FIXES +#define STLPLUS_PORTABILITY_FIXES +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Contains work arounds for OS or Compiler specific problems to try to make +// them look more alike + +// It is strongly recommended that this header be included as the first +// #include in every source file + +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Problem with MicroSoft defining two different macros to identify Windows +//////////////////////////////////////////////////////////////////////////////// + +#if defined(_WIN32) || defined(_WIN32_WCE) +#define MSWINDOWS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Problems with unnecessary or unfixable compiler warnings +//////////////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +// Microsoft Visual Studio +// shut up the following irritating warnings +// 4786 - VC6, identifier string exceeded maximum allowable length and was truncated (only affects debugger) +// 4305 - VC6, identifier type was converted to a smaller type +// 4503 - VC6, decorated name was longer than the maximum the compiler allows (only affects debugger) +// 4309 - VC6, type conversion operation caused a constant to exceeded the space allocated for it +// 4290 - VC6, C++ exception specification ignored +// 4800 - VC6, forcing value to bool 'true' or 'false' (performance warning) +// 4675 - VC7.1, "change" in function overload resolution _might_ have altered program +// 4996 - VC8, 'xxxx' was declared deprecated +#pragma warning(disable: 4786 4305 4503 4309 4290 4800 4675 4996) +#endif + +#ifdef __BORLANDC__ +// Borland +// Shut up the following irritating warnings +// 8026 - Functions with exception specifications are not expanded inline +// 8027 - Functions with xxx are not expanded inline +#pragma warn -8026 +#pragma warn -8027 +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Problems with redefinition of min/max in various different versions of library headers +//////////////////////////////////////////////////////////////////////////////// + +// The Windows headers define macros called max/min which conflict with the templates std::max and std::min. +// So, to avoid conflicts, MS removed the std::max/min rather than fixing the problem! +// From Visual Studio .NET (SV7, compiler version 13.00) the STL templates have been added correctly. +// For MFC compatibility, only undef min and max in non-MFC programs - some bits of MFC +// use macro min/max in headers. + +// I've created extra template function definitions minimum/maximum that avoid all the problems above + +namespace stlplus +{ + template const T& maximum(const T& l, const T& r) {return l > r ? l : r;} + template const T& minimum(const T& l, const T& r) {return l < r ? l : r;} +} + +//////////////////////////////////////////////////////////////////////////////// +// Problems with differences between namespaces +//////////////////////////////////////////////////////////////////////////////// + +// Note: not sure of the relevance of this - maybe deprecated? +// problem in gcc pre-v3 where the sub-namespaces in std aren't present +// this mean that the statement "using namespace std::rel_ops" created an error because the namespace didn't exist + +// I've done a fix here that creates an empty namespace for this case, but I +// do *not* try to move the contents of std::rel_ops into namespace std +// This fix only works if you use "using namespace std::rel_ops" to bring in the template relational operators (e.g. != defined i.t.o. ==) + +#ifdef __GNUC__ +namespace std +{ + namespace rel_ops + { + } +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// problems with missing functions +//////////////////////////////////////////////////////////////////////////////// + +#ifdef MSWINDOWS +unsigned sleep(unsigned seconds); +#else +#include +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Function for establishing endian-ness +//////////////////////////////////////////////////////////////////////////////// +// Different machine architectures store data using different byte orders. +// This is referred to as Big- and Little-Endian Byte Ordering. +// +// The issue is: where does a pointer to an integer type actually point? +// +// In both conventions, the address points to the left of the word but: +// Big-Endian - The most significant byte is on the left end of a word +// Little-Endian - The least significant byte is on the left end of a word +// +// Bytes are addressed left to right, so in big-endian order byte 0 is the +// msB, whereas in little-endian order byte 0 is the lsB. For example, +// Intel-based machines store data in little-endian byte order so byte 0 is +// the lsB. +// +// This function establishes byte order at run-time + +namespace stlplus +{ + bool little_endian(void); +} + +//////////////////////////////////////////////////////////////////////////////// +#endif diff --git a/src/stlplus/portability/subprocesses.cpp b/src/stlplus/portability/subprocesses.cpp index b6c791b..782b994 100644 --- a/src/stlplus/portability/subprocesses.cpp +++ b/src/stlplus/portability/subprocesses.cpp @@ -1,2076 +1,2116 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// - -// Bug fix by Alistair Low: kill on Windows now kills grandchild processes as -// well as the child process. This is done using jobs - which has to be -// enabled by stating that the version of Windows is at least 5.0 -#if defined(_WIN32) || defined(_WIN32_WCE) -#define _WIN32_WINNT 0x0500 -#endif - -#include "subprocesses.hpp" -#include "file_system.hpp" -#include "dprintf.hpp" -#include -#include -#include - -#ifdef MSWINDOWS -#else -#include -#include -#include -#include -#include -extern char** environ; -#endif - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // argument-vector related stuff - - static void skip_white (const std::string& command, unsigned& i) - { - while(i < command.size() && isspace(command[i])) - i++; - } - - // get_argument is the main function for breaking a string down into separate command arguments - // it mimics the way shells break down a command into an argv[] and unescapes the escaped characters on the way - - static std::string get_argument (const std::string& command, unsigned& i) - { - std::string result; -#ifdef MSWINDOWS - - // as far as I know, there is only double-quoting and no escape character in DOS - // so, how do you include a double-quote in an argument??? - - bool dquote = false; - for ( ; i < command.size(); i++) - { - char ch = command[i]; - if (!dquote && isspace(ch)) break; - if (dquote) - { - if (ch == '\"') - dquote = false; - else - result += ch; - } - else if (ch == '\"') - dquote = true; - else - result += ch; - } -#else - bool squote = false; - bool dquote = false; - bool escaped = false; - for ( ; i < command.size(); i++) - { - char ch = command[i]; - if (!squote && !dquote && !escaped && isspace(ch)) break; - if (escaped) - { - result += ch; - escaped = false; - } - else if (squote) - { - if (ch == '\'') - squote = false; - else - result += ch; - } - else if (ch == '\\') - escaped = true; - else if (dquote) - { - if (ch == '\"') - dquote = false; - else - result += ch; - } - else if (ch == '\'') - squote = true; - else if (ch == '\"') - dquote = true; - else - result += ch; - } -#endif - - return result; - } - - - // this function performs the reverse of the above on a single argument - // it escapes special characters and quotes the argument if necessary ready for shell interpretation - - static std::string make_argument (const std::string& arg) - { - std::string result; - bool needs_quotes = false; - - for (unsigned i = 0; i < arg.size(); i++) - { - switch (arg[i]) - { - // set of characters requiring escapes -#ifdef MSWINDOWS -#else - case '\\': case '\'': case '\"': case '`': case '(': case ')': - case '&': case '|': case '<': case '>': case '*': case '?': case '!': - result += '\\'; - result += arg[i]; - break; -#endif - // set of whitespace characters that force quoting - case ' ': - result += arg[i]; - needs_quotes = true; - break; - default: - result += arg[i]; - break; - } - } - - if (needs_quotes) - { - result.insert(result.begin(), '"'); - result += '"'; - } - return result; - } - - static char* copy_string (const char* str) - { - char* result = new char[strlen(str)+1]; - strcpy(result,str); - return result; - } - - //////////////////////////////////////////////////////////////////////////////// - - arg_vector::arg_vector (void) - { - m_argv = 0; - } - - arg_vector::arg_vector (const arg_vector& a) - { - m_argv = 0; - *this = a; - } - - arg_vector::arg_vector (char** a) - { - m_argv = 0; - *this = a; - } - - arg_vector::arg_vector (const std::string& command) - { - m_argv = 0; - *this = command; - } - - arg_vector::arg_vector (const char* command) - { - m_argv = 0; - *this = command; - } - - arg_vector::~arg_vector (void) - { - clear(); - } - - arg_vector& arg_vector::operator = (const arg_vector& a) - { - return *this = a.m_argv; - } - - arg_vector& arg_vector::operator = (char** argv) - { - clear(); - for (unsigned i = 0; argv[i]; i++) - operator += (argv[i]); - return *this; - } - - arg_vector& arg_vector::operator = (const std::string& command) - { - clear(); - for (unsigned i = 0; i < command.size(); ) - { - std::string argument = get_argument(command, i); - operator += (argument); - skip_white(command, i); - } - return *this; - } - - arg_vector& arg_vector::operator = (const char* command) - { - return operator = (std::string(command)); - } - - arg_vector& arg_vector::operator += (const std::string& str) - { - insert(size(), str); - return *this; - } - - arg_vector& arg_vector::operator -= (const std::string& str) - { - insert(0, str); - return *this; - } - - void arg_vector::insert (unsigned index, const std::string& str) throw(std::out_of_range) - { - if (index > size()) throw std::out_of_range("arg_vector::insert"); - // copy up to but not including index, then add the new argument, then copy the rest - char** new_argv = new char*[size()+2]; - unsigned i = 0; - for ( ; i < index; i++) - new_argv[i] = copy_string(m_argv[i]); - new_argv[index] = copy_string(str.c_str()); - for ( ; i < size(); i++) - new_argv[i+1] = copy_string(m_argv[i]); - new_argv[i+1] = 0; - clear(); - m_argv = new_argv; - } - - void arg_vector::clear (unsigned index) throw(std::out_of_range) - { - if (index >= size()) throw std::out_of_range("arg_vector::clear"); - // copy up to index, skip it, then copy the rest - char** new_argv = new char*[size()]; - unsigned i = 0; - for ( ; i < index; i++) - new_argv[i] = copy_string(m_argv[i]); - i++; - for ( ; i < size(); i++) - new_argv[i-1] = copy_string(m_argv[i]); - new_argv[i-1] = 0; - clear(); - m_argv = new_argv; - } - - void arg_vector::clear(void) - { - if (m_argv) - { - for (unsigned i = 0; m_argv[i]; i++) - delete[] m_argv[i]; - delete[] m_argv; - m_argv = 0; - } - } - - unsigned arg_vector::size (void) const - { - unsigned i = 0; - if (m_argv) - while (m_argv[i]) - i++; - return i; - } - - arg_vector::operator char** (void) const - { - return m_argv; - } - - char** arg_vector::argv (void) const - { - return m_argv; - } - - char* arg_vector::operator [] (unsigned index) const throw(std::out_of_range) - { - if (index >= size()) throw std::out_of_range("arg_vector::operator[]"); - return m_argv[index]; - } - - char* arg_vector::argv0 (void) const throw(std::out_of_range) - { - return operator [] (0); - } - - std::string arg_vector::image (void) const - { - std::string result; - for (unsigned i = 0; i < size(); i++) - { - if (i) result += ' '; - result += make_argument(m_argv[i]); - } - return result; - } - - //////////////////////////////////////////////////////////////////////////////// - // environment-vector - - // Windoze environment is a single string containing null-terminated - // name=value strings and the whole terminated by a null - - // Unix environment is a null-terminated vector of pointers to null-terminated strings - - //////////////////////////////////////////////////////////////////////////////// - // platform specifics - -#ifdef MSWINDOWS - // Windows utilities - - // Windows environment variables are case-insensitive and I do comparisons by converting to lowercase - static std::string lowercase(const std::string& val) - { - std::string text = val; - for (unsigned i = 0; i < text.size(); i++) - text[i] = tolower(text[i]); - return text; - } - - static unsigned envp_size(const char* envp) - { - unsigned size = 0; - while (envp[size] || (size > 0 && envp[size-1])) size++; - size++; - return size; - } - - static void envp_extract(std::string& name, std::string& value, const char* envp, unsigned& envi) - { - name.erase(); - value.erase(); - if (!envp[envi]) return; - // some special variables start with '=' so ensure at least one character in the name - name += envp[envi++]; - while(envp[envi] != '=') - name += envp[envi++]; - envi++; - while(envp[envi]) - value += envp[envi++]; - envi++; - } - - static void envp_append(const std::string& name, const std::string& value, char* envp, unsigned& envi) - { - for (unsigned i = 0; i < name.size(); i++) - envp[envi++] = name[i]; - envp[envi++] = '='; - for (unsigned j = 0; j < value.size(); j++) - envp[envi++] = value[j]; - envp[envi++] = '\0'; - envp[envi] = '\0'; - } - - static char* envp_copy(const char* envp) - { - unsigned size = envp_size(envp); - char* result = new char[size]; - result[0] = '\0'; - unsigned i = 0; - unsigned j = 0; - while(envp[i]) - { - std::string name; - std::string value; - envp_extract(name, value, envp, i); - envp_append(name, value, result, j); - } - return result; - } - - static void envp_clear(char*& envp) - { - if (envp) - { - delete[] envp; - envp = 0; - } - } - - static bool envp_equal(const std::string& left, const std::string& right) - { - return lowercase(left) == lowercase(right); - } - - static bool envp_less(const std::string& left, const std::string& right) - { - return lowercase(left) < lowercase(right); - } - -#else - // Unix utilities - - static unsigned envp_size(char* const* envp) - { - unsigned size = 0; - while(envp[size]) size++; - size++; - return size; - } - - static void envp_extract(std::string& name, std::string& value, char* const* envp, unsigned& envi) - { - name = ""; - value = ""; - if (!envp[envi]) return; - unsigned i = 0; - while(envp[envi][i] != '=') - name += envp[envi][i++]; - i++; - while(envp[envi][i]) - value += envp[envi][i++]; - envi++; - } - - static void envp_append(const std::string& name, const std::string& value, char** envp, unsigned& envi) - { - std::string entry = name + "=" + value; - envp[envi] = copy_string(entry.c_str()); - envi++; - envp[envi] = 0; - } - - static char** envp_copy(char* const* envp) - { - unsigned size = envp_size(envp); - char** result = new char*[size]; - unsigned i = 0; - unsigned j = 0; - while(envp[i]) - { - std::string name; - std::string value; - envp_extract(name, value, envp, i); - envp_append(name, value, result, j); - } - return result; - } - - static void envp_clear(char**& envp) - { - if (envp) - { - for (unsigned i = 0; envp[i]; i++) - delete[] envp[i]; - delete[] envp; - envp = 0; - } - } - - static bool envp_equal(const std::string& left, const std::string& right) - { - return left == right; - } - - static bool envp_less(const std::string& left, const std::string& right) - { - return left < right; - } - -#endif - //////////////////////////////////////////////////////////////////////////////// - - env_vector::env_vector(void) - { -#ifdef MSWINDOWS - char* env = (char*)GetEnvironmentStringsA(); - m_env = envp_copy(env); - FreeEnvironmentStringsA(env); -#else - m_env = envp_copy(::environ); -#endif - } - - env_vector::env_vector (const env_vector& a) - { - m_env = 0; - *this = a; - } - - env_vector::~env_vector (void) - { - clear(); - } - - env_vector& env_vector::operator = (const env_vector& a) - { - clear(); - m_env = envp_copy(a.m_env); - return *this; - } - - void env_vector::clear(void) - { - envp_clear(m_env); - } - - void env_vector::add(const std::string& name, const std::string& value) - { - // the trick is to add the value in alphabetic order - // this is done by copying the existing environment string to a new - // string, inserting the new value when a name greater than it is found - unsigned size = envp_size(m_env); -#ifdef MSWINDOWS - unsigned new_size = size + name.size() + value.size() + 2; - char* new_v = new char[new_size]; - new_v[0] = '\0'; -#else - unsigned new_size = size + 1; - char** new_v = new char*[new_size]; - new_v[0] = 0; -#endif - // now extract each name=value pair and check the ordering - bool added = false; - unsigned i = 0; - unsigned j = 0; - while(m_env[i]) - { - std::string current_name; - std::string current_value; - envp_extract(current_name, current_value, m_env, i); - if (envp_equal(name,current_name)) - { - // replace an existing value - envp_append(name, value, new_v, j); - } - else if (!added && envp_less(name,current_name)) - { - // add the new value first, then the existing one - envp_append(name, value, new_v, j); - envp_append(current_name, current_value, new_v, j); - added = true; - } - else - { - // just add the existing value - envp_append(current_name, current_value, new_v, j); - } - } - if (!added) - envp_append(name, value, new_v, j); - envp_clear(m_env); - m_env = new_v; - } - - - bool env_vector::remove (const std::string& name) - { - bool result = false; - // this is done by copying the existing environment string to a new string, but excluding the specified name - unsigned size = envp_size(m_env); -#ifdef MSWINDOWS - char* new_v = new char[size]; - new_v[0] = '\0'; -#else - char** new_v = new char*[size]; - new_v[0] = 0; -#endif - unsigned i = 0; - unsigned j = 0; - while(m_env[i]) - { - std::string current_name; - std::string current_value; - envp_extract(current_name, current_value, m_env, i); - if (envp_equal(name,current_name)) - result = true; - else - envp_append(current_name, current_value, new_v, j); - } - envp_clear(m_env); - m_env = new_v; - return result; - } - - std::string env_vector::operator [] (const std::string& name) const - { - return get(name); - } - - std::string env_vector::get (const std::string& name) const - { - unsigned i = 0; - while(m_env[i]) - { - std::string current_name; - std::string current_value; - envp_extract(current_name, current_value, m_env, i); - if (envp_equal(name,current_name)) - return current_value; - } - return std::string(); - } - - unsigned env_vector::size (void) const - { - unsigned i = 0; -#ifdef MSWINDOWS - unsigned offset = 0; - while(m_env[offset]) - { - std::string current_name; - std::string current_value; - envp_extract(current_name, current_value, m_env, offset); - i++; - } -#else - while(m_env[i]) - i++; -#endif - - return i; - } - - std::pair env_vector::operator [] (unsigned index) const throw(std::out_of_range) - { - return get(index); - } - - std::pair env_vector::get (unsigned index) const throw(std::out_of_range) - { - if (index >= size()) throw std::out_of_range("arg_vector::get"); - unsigned j = 0; - for (unsigned i = 0; i < index; i++) - { - std::string current_name; - std::string current_value; - envp_extract(current_name, current_value, m_env, j); - } - std::string name; - std::string value; - envp_extract(name, value, m_env, j); - return std::make_pair(name,value); - } - - ENVIRON_TYPE env_vector::envp (void) const - { - return m_env; - } - - //////////////////////////////////////////////////////////////////////////////// - // Synchronous subprocess - // Win32 implementation mostly cribbed from MSDN examples and then made (much) more readable - // Unix implementation mostly from man pages and bitter experience - //////////////////////////////////////////////////////////////////////////////// - -#ifdef MSWINDOWS - - subprocess::subprocess(void) - { - m_pid.hProcess = 0; - m_job = 0; - m_child_in = 0; - m_child_out = 0; - m_child_err = 0; - m_err = 0; - m_status = 0; - } - -#else - - subprocess::subprocess(void) - { - m_pid = -1; - m_child_in = -1; - m_child_out = -1; - m_child_err = -1; - m_err = 0; - m_status = 0; - } - -#endif - -#ifdef MSWINDOWS - - subprocess::~subprocess(void) - { - if (m_pid.hProcess != 0) - { - close_stdin(); - close_stdout(); - close_stderr(); - kill(); - WaitForSingleObject(m_pid.hProcess, INFINITE); - CloseHandle(m_pid.hThread); - CloseHandle(m_pid.hProcess); - CloseHandle(m_job); - } - } - -#else - - subprocess::~subprocess(void) - { - if (m_pid != -1) - { - close_stdin(); - close_stdout(); - close_stderr(); - kill(); - for (;;) - { - int wait_status = 0; - int wait_ret_val = waitpid(m_pid, &wait_status, 0); - if (wait_ret_val != -1 || errno != EINTR) break; - } - } - } - -#endif - - void subprocess::add_variable(const std::string& name, const std::string& value) - { - m_env.add(name, value); - } - - bool subprocess::remove_variable(const std::string& name) - { - return m_env.remove(name); - } - -#ifdef MSWINDOWS - - bool subprocess::spawn(const std::string& path, const arg_vector& argv, - bool connect_stdin, bool connect_stdout, bool connect_stderr) - { - bool result = true; - // first create the pipes to be used to connect to the child stdin/out/err - // If no pipes requested, then connect to the parent stdin/out/err - // for some reason you have to create a pipe handle, then duplicate it - // This is not well explained in MSDN but seems to work - PIPE_TYPE parent_stdin = 0; - if (!connect_stdin) - parent_stdin = GetStdHandle(STD_INPUT_HANDLE); - else - { - PIPE_TYPE tmp = 0; - SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; - CreatePipe(&parent_stdin, &tmp, &inherit_handles, 0); - DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_in, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); - } - - PIPE_TYPE parent_stdout = 0; - if (!connect_stdout) - parent_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - else - { - PIPE_TYPE tmp = 0; - SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; - CreatePipe(&tmp, &parent_stdout, &inherit_handles, 0); - DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_out, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); - } - - PIPE_TYPE parent_stderr = 0; - if (!connect_stderr) - parent_stderr = GetStdHandle(STD_ERROR_HANDLE); - else - { - PIPE_TYPE tmp = 0; - SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; - CreatePipe(&tmp, &parent_stderr, &inherit_handles, 0); - DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_err, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); - } - - // Now create the subprocess - // The horrible trick of creating a console window and hiding it seems to be required for the pipes to work - // Note that the child will inherit a copy of the pipe handles - STARTUPINFOA startup = {sizeof(STARTUPINFO),0,0,0,0,0,0,0,0,0,0, - STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW,SW_HIDE,0,0, - parent_stdin,parent_stdout,parent_stderr}; - bool created = CreateProcessA(path.c_str(),(char*)argv.image().c_str(),0,0,TRUE,CREATE_SUSPENDED,m_env.envp(),0,&startup,&m_pid) != 0; - // close the parent copy of the pipe handles so that the pipes will be closed when the child releases them - if (connect_stdin) CloseHandle(parent_stdin); - if (connect_stdout) CloseHandle(parent_stdout); - if (connect_stderr) CloseHandle(parent_stderr); - if (!created) - { - m_err = GetLastError(); - close_stdin(); - close_stdout(); - close_stderr(); - result = false; - } - else - { - m_job = CreateJobObject(NULL, NULL); - AssignProcessToJobObject(m_job, m_pid.hProcess); - ResumeThread(m_pid.hThread); - - // The child process is now running so call the user's callback - // The convention is that the callback can return false, in which case this will kill the child (if its still running) - if (!callback()) - { - result = false; - kill(); - } - close_stdin(); - close_stdout(); - close_stderr(); - // wait for the child to finish - // TODO - kill the child if a timeout happens - WaitForSingleObject(m_pid.hProcess, INFINITE); - DWORD exit_status = 0; - if (!GetExitCodeProcess(m_pid.hProcess, &exit_status)) - { - m_err = GetLastError(); - result = false; - } - else if (exit_status != 0) - result = false; - m_status = (int)exit_status; - CloseHandle(m_pid.hThread); - CloseHandle(m_pid.hProcess); - CloseHandle(m_job); - } - m_pid.hProcess = 0; - return result; - } - -#else - - bool subprocess::spawn(const std::string& path, const arg_vector& argv, - bool connect_stdin, bool connect_stdout, bool connect_stderr) - { - bool result = true; - // first create the pipes to be used to connect to the child stdin/out/err - - int stdin_pipe [2] = {-1, -1}; - if (connect_stdin) - pipe(stdin_pipe); - - int stdout_pipe [2] = {-1, -1}; - if (connect_stdout) - pipe(stdout_pipe); - - int stderr_pipe [2] = {-1, -1}; - if (connect_stderr) - pipe(stderr_pipe); - - // now create the subprocess - // In Unix, this is done by forking (creating two copies of the parent), then overwriting the child copy using exec - m_pid = ::fork(); - switch(m_pid) - { - case -1: // failed to fork - m_err = errno; - if (connect_stdin) - { - ::close(stdin_pipe[0]); - ::close(stdin_pipe[1]); - } - if (connect_stdout) - { - ::close(stdout_pipe[0]); - ::close(stdout_pipe[1]); - } - if (connect_stderr) - { - ::close(stderr_pipe[0]); - ::close(stderr_pipe[1]); - } - result = false; - break; - case 0: // in child; - { - // for each pipe, close the end of the duplicated pipe that is being used by the parent - // and connect the child's end of the pipe to the appropriate standard I/O device - if (connect_stdin) - { - ::close(stdin_pipe[1]); - dup2(stdin_pipe[0],STDIN_FILENO); - } - if (connect_stdout) - { - ::close(stdout_pipe[0]); - dup2(stdout_pipe[1],STDOUT_FILENO); - } - if (connect_stderr) - { - ::close(stderr_pipe[0]); - dup2(stderr_pipe[1],STDERR_FILENO); - } - execve(path.c_str(), argv.argv(), m_env.envp()); - // will only ever get here if the exec() failed completely - *must* now exit the child process - // by using errno, the parent has some chance of diagnosing the cause of the problem - exit(errno); - } - break; - default: // in parent - { - // for each pipe, close the end of the duplicated pipe that is being used by the child - // and connect the parent's end of the pipe to the class members so that they are visible to the parent() callback - if (connect_stdin) - { - ::close(stdin_pipe[0]); - m_child_in = stdin_pipe[1]; - } - if (connect_stdout) - { - ::close(stdout_pipe[1]); - m_child_out = stdout_pipe[0]; - } - if (connect_stderr) - { - ::close(stderr_pipe[1]); - m_child_err = stderr_pipe[0]; - } - // call the user's callback - if (!callback()) - { - result = false; - kill(); - } - // close the pipes and wait for the child to finish - // wait exits on a signal which may be the child signalling its exit or may be an interrupt - close_stdin(); - close_stdout(); - close_stderr(); - int wait_status = 0; - for (;;) - { - int wait_ret_val = waitpid(m_pid, &wait_status, 0); - if (wait_ret_val != -1 || errno != EINTR) break; - } - // establish whether an error occurred - if (WIFSIGNALED(wait_status)) - { - // set_error(errno); - m_status = WTERMSIG(wait_status); - result = false; - } - else if (WIFEXITED(wait_status)) - { - m_status = WEXITSTATUS(wait_status); - if (m_status != 0) - result = false; - } - m_pid = -1; - } - break; - } - return result; - } - -#endif - - bool subprocess::spawn(const std::string& command_line, - bool connect_stdin, bool connect_stdout, bool connect_stderr) - { - arg_vector arguments = command_line; - if (arguments.size() == 0) return false; - std::string path = path_lookup(arguments.argv0()); - if (path.empty()) return false; - return spawn(path, arguments, connect_stdin, connect_stdout, connect_stderr); - } - - bool subprocess::callback(void) - { - return true; - } - -#ifdef MSWINDOWS - - bool subprocess::kill (void) - { - if (!m_pid.hProcess) return false; - close_stdin(); - close_stdout(); - close_stderr(); - if (!TerminateJobObject(m_job, (UINT)-1)) - { - m_err = GetLastError(); - return false; - } - return true; - } - -#else - - bool subprocess::kill (void) - { - if (m_pid == -1) return false; - close_stdin(); - close_stdout(); - close_stderr(); - if (::kill(m_pid, SIGINT) == -1) - { - m_err = errno; - return false; - } - return true; - } - -#endif - -#ifdef MSWINDOWS - - int subprocess::write_stdin (std::string& buffer) - { - if (m_child_in == 0) return -1; - // do a blocking write of the whole buffer - DWORD bytes = 0; - if (!WriteFile(m_child_in, buffer.c_str(), (DWORD)buffer.size(), &bytes, 0)) - { - m_err = GetLastError(); - close_stdin(); - return -1; - } - // now discard that part of the buffer that was written - if (bytes > 0) - buffer.erase(0, bytes); - return bytes; - } - -#else - - int subprocess::write_stdin (std::string& buffer) - { - if (m_child_in == -1) return -1; - // do a blocking write of the whole buffer - int bytes = write(m_child_in, buffer.c_str(), buffer.size()); - if (bytes == -1) - { - m_err = errno; - close_stdin(); - return -1; - } - // now discard that part of the buffer that was written - if (bytes > 0) - buffer.erase(0, bytes); - return bytes; - } - -#endif - -#ifdef MSWINDOWS - - int subprocess::read_stdout (std::string& buffer) - { - if (m_child_out == 0) return -1; - DWORD bytes = 0; - DWORD buffer_size = 256; - char* tmp = new char[buffer_size]; - if (!ReadFile(m_child_out, tmp, buffer_size, &bytes, 0)) - { - if (GetLastError() != ERROR_BROKEN_PIPE) - m_err = GetLastError(); - close_stdout(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stdout(); - delete[] tmp; - return -1; - } - buffer.append(tmp, bytes); - delete[] tmp; - return (int)bytes; - } - -#else - - int subprocess::read_stdout (std::string& buffer) - { - if (m_child_out == -1) return -1; - int buffer_size = 256; - char* tmp = new char[buffer_size]; - int bytes = read(m_child_out, tmp, buffer_size); - if (bytes == -1) - { - m_err = errno; - close_stdout(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stdout(); - delete[] tmp; - return -1; - } - buffer.append(tmp, bytes); - delete[] tmp; - return bytes; - } - -#endif - -#ifdef MSWINDOWS - - int subprocess::read_stderr(std::string& buffer) - { - if (m_child_err == 0) return -1; - DWORD bytes = 0; - DWORD buffer_size = 256; - char* tmp = new char[buffer_size]; - if (!ReadFile(m_child_err, tmp, buffer_size, &bytes, 0)) - { - if (GetLastError() != ERROR_BROKEN_PIPE) - m_err = GetLastError(); - close_stderr(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stderr(); - delete[] tmp; - return -1; - } - buffer.append(tmp, bytes); - delete[] tmp; - return (int)bytes; - } - -#else - - int subprocess::read_stderr (std::string& buffer) - { - if (m_child_err == -1) return -1; - int buffer_size = 256; - char* tmp = new char[buffer_size]; - int bytes = read(m_child_err, tmp, buffer_size); - if (bytes == -1) - { - m_err = errno; - close_stderr(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stderr(); - delete[] tmp; - return -1; - } - buffer.append(tmp, bytes); - delete[] tmp; - return bytes; - } - -#endif - -#ifdef MSWINDOWS - - void subprocess::close_stdin (void) - { - if (m_child_in) - { - CloseHandle(m_child_in); - m_child_in = 0; - } - } - -#else - - void subprocess::close_stdin (void) - { - if (m_child_in != -1) - { - ::close(m_child_in); - m_child_in = -1; - } - } - -#endif - -#ifdef MSWINDOWS - - void subprocess::close_stdout (void) - { - if (m_child_out) - { - CloseHandle(m_child_out); - m_child_out = 0; - } - } - -#else - - void subprocess::close_stdout (void) - { - if (m_child_out != -1) - { - ::close(m_child_out); - m_child_out = -1; - } - } - -#endif - -#ifdef MSWINDOWS - - void subprocess::close_stderr (void) - { - if (m_child_err) - { - CloseHandle(m_child_err); - m_child_err = 0; - } - } - -#else - - void subprocess::close_stderr (void) - { - if (m_child_err != -1) - { - ::close(m_child_err); - m_child_err = -1; - } - } - -#endif - - bool subprocess::error(void) const - { - return m_err != 0; - } - - int subprocess::error_number(void) const - { - return m_err; - } - -#ifdef MSWINDOWS - - std::string subprocess::error_text(void) const - { - if (m_err == 0) return std::string(); - char* message; - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, - 0, - m_err, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR)&message, - 0,0); - std::string result = message; - LocalFree(message); - // the error message is for some perverse reason newline terminated - remove this - if (result[result.size()-1] == '\n') - result.erase(result.end()-1); - if (result[result.size()-1] == '\r') - result.erase(result.end()-1); - return result; - } - -#else - - std::string subprocess::error_text(void) const - { - if (m_err == 0) return std::string(); - char* text = strerror(m_err); - if (text) return std::string(text); - return "error number " + dformat("%d",m_err); - } - -#endif - - int subprocess::exit_status(void) const - { - return m_status; - } - - //////////////////////////////////////////////////////////////////////////////// - // backtick subprocess and operations - - backtick_subprocess::backtick_subprocess(void) : subprocess() - { - } - - bool backtick_subprocess::callback(void) - { - for (;;) - { - std::string buffer; - int read_size = read_stdout(buffer); - if (read_size < 0) break; - m_text += buffer; - } - return !error(); - } - - bool backtick_subprocess::spawn(const std::string& path, const arg_vector& argv) - { - return subprocess::spawn(path, argv, false, true, false); - } - - bool backtick_subprocess::spawn(const std::string& command_line) - { - return subprocess::spawn(command_line, false, true, false); - } - - std::vector backtick_subprocess::text(void) const - { - std::vector result; - // convert the raw text into a vector of strings, each corresponding to a line - // in the process, strip out platform-specific line-endings - for (unsigned i = 0; i < m_text.size(); i++) - { - // handle any kind of line-ending - Dos, Unix or MacOS - switch(m_text[i]) - { - case '\xd': // carriage-return - optionally followed by linefeed - { - // discard optional following linefeed - if ((i+1 < m_text.size()) && (m_text[i+1] == '\xa')) - i++; - // add a new line to the end of the vector - result.push_back(std::string()); - break; - } - case '\xa': // linefeed - { - // add a new line to the end of the vector - result.push_back(std::string()); - break; - } - default: - { - result.back() += m_text[i]; - break; - } - } - } - // tidy up - if the last line ended with a newline, the vector will end with an empty string - discard this - if ((result.size()) > 0 && result.back().empty()) - result.erase(result.end()-1); - return result; - } - - std::vector backtick(const std::string& path, const arg_vector& argv) - { - backtick_subprocess sub; - sub.spawn(path, argv); - return sub.text(); - } - - std::vector backtick(const std::string& command_line) - { - backtick_subprocess sub; - sub.spawn(command_line); - return sub.text(); - } - - //////////////////////////////////////////////////////////////////////////////// - // Asynchronous subprocess - -#ifdef MSWINDOWS - - async_subprocess::async_subprocess(void) - { - m_pid.hProcess = 0; - m_job = 0; - m_child_in = 0; - m_child_out = 0; - m_child_err = 0; - m_err = 0; - m_status = 0; - } - -#else - - async_subprocess::async_subprocess(void) - { - m_pid = -1; - m_child_in = -1; - m_child_out = -1; - m_child_err = -1; - m_err = 0; - m_status = 0; - } - -#endif - -#ifdef MSWINDOWS - - async_subprocess::~async_subprocess(void) - { - if (m_pid.hProcess != 0) - { - close_stdin(); - close_stdout(); - close_stderr(); - kill(); - WaitForSingleObject(m_pid.hProcess, INFINITE); - CloseHandle(m_pid.hThread); - CloseHandle(m_pid.hProcess); - CloseHandle(m_job); - } - } - -#else - - async_subprocess::~async_subprocess(void) - { - if (m_pid != -1) - { - close_stdin(); - close_stdout(); - close_stderr(); - kill(); - for (;;) - { - int wait_status = 0; - int wait_ret_val = waitpid(m_pid, &wait_status, 0); - if (wait_ret_val != -1 || errno != EINTR) break; - } - } - } - -#endif - - void async_subprocess::set_error(int e) - { - m_err = e; - } - - void async_subprocess::add_variable(const std::string& name, const std::string& value) - { - m_env.add(name, value); - } - - bool async_subprocess::remove_variable(const std::string& name) - { - return m_env.remove(name); - } - -#ifdef MSWINDOWS - - bool async_subprocess::spawn(const std::string& path, const arg_vector& argv, - bool connect_stdin, bool connect_stdout, bool connect_stderr) - { - bool result = true; - // first create the pipes to be used to connect to the child stdin/out/err - // If no pipes requested, then connect to the parent stdin/out/err - // for some reason you have to create a pipe handle, then duplicate it - // This is not well explained in MSDN but seems to work - PIPE_TYPE parent_stdin = 0; - if (!connect_stdin) - parent_stdin = GetStdHandle(STD_INPUT_HANDLE); - else - { - PIPE_TYPE tmp = 0; - SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; - CreatePipe(&parent_stdin, &tmp, &inherit_handles, 0); - DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_in, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); - } - - PIPE_TYPE parent_stdout = 0; - if (!connect_stdout) - parent_stdout = GetStdHandle(STD_OUTPUT_HANDLE); - else - { - PIPE_TYPE tmp = 0; - SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; - CreatePipe(&tmp, &parent_stdout, &inherit_handles, 0); - DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_out, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); - } - - PIPE_TYPE parent_stderr = 0; - if (!connect_stderr) - parent_stderr = GetStdHandle(STD_ERROR_HANDLE); - else - { - PIPE_TYPE tmp = 0; - SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; - CreatePipe(&tmp, &parent_stderr, &inherit_handles, 0); - DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_err, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); - } - - // Now create the subprocess - // The horrible trick of creating a console window and hiding it seems to be required for the pipes to work - // Note that the child will inherit a copy of the pipe handles - STARTUPINFOA startup = {sizeof(STARTUPINFO),0,0,0,0,0,0,0,0,0,0, - STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW,SW_HIDE,0,0, - parent_stdin,parent_stdout,parent_stderr}; - bool created = CreateProcessA(path.c_str(),(char*)argv.image().c_str(),0,0,TRUE,CREATE_SUSPENDED,m_env.envp(),0,&startup,&m_pid) != 0; - // close the parent copy of the pipe handles so that the pipes will be closed when the child releases them - if (connect_stdin) CloseHandle(parent_stdin); - if (connect_stdout) CloseHandle(parent_stdout); - if (connect_stderr) CloseHandle(parent_stderr); - if (!created) - { - set_error(GetLastError()); - close_stdin(); - close_stdout(); - close_stderr(); - result = false; - } - else - { - m_job = CreateJobObject(NULL, NULL); - AssignProcessToJobObject(m_job, m_pid.hProcess); - ResumeThread(m_pid.hThread); - } - return result; - } - -#else - - bool async_subprocess::spawn(const std::string& path, const arg_vector& argv, - bool connect_stdin, bool connect_stdout, bool connect_stderr) - { - bool result = true; - // first create the pipes to be used to connect to the child stdin/out/err - - int stdin_pipe [2] = {-1, -1}; - if (connect_stdin) - pipe(stdin_pipe); - - int stdout_pipe [2] = {-1, -1}; - if (connect_stdout) - pipe(stdout_pipe); - - int stderr_pipe [2] = {-1, -1}; - if (connect_stderr) - pipe(stderr_pipe); - - // now create the subprocess - // In Unix, this is done by forking (creating two copies of the parent), then overwriting the child copy using exec - m_pid = ::fork(); - switch(m_pid) - { - case -1: // failed to fork - set_error(errno); - if (connect_stdin) - { - ::close(stdin_pipe[0]); - ::close(stdin_pipe[1]); - } - if (connect_stdout) - { - ::close(stdout_pipe[0]); - ::close(stdout_pipe[1]); - } - if (connect_stderr) - { - ::close(stderr_pipe[0]); - ::close(stderr_pipe[1]); - } - result = false; - break; - case 0: // in child; - { - // for each pipe, close the end of the duplicated pipe that is being used by the parent - // and connect the child's end of the pipe to the appropriate standard I/O device - if (connect_stdin) - { - ::close(stdin_pipe[1]); - dup2(stdin_pipe[0],STDIN_FILENO); - } - if (connect_stdout) - { - ::close(stdout_pipe[0]); - dup2(stdout_pipe[1],STDOUT_FILENO); - } - if (connect_stderr) - { - ::close(stderr_pipe[0]); - dup2(stderr_pipe[1],STDERR_FILENO); - } - execve(path.c_str(), argv.argv(), m_env.envp()); - // will only ever get here if the exec() failed completely - *must* now exit the child process - // by using errno, the parent has some chance of diagnosing the cause of the problem - exit(errno); - } - break; - default: // in parent - { - // for each pipe, close the end of the duplicated pipe that is being used by the child - // and connect the parent's end of the pipe to the class members so that they are visible to the parent() callback - if (connect_stdin) - { - ::close(stdin_pipe[0]); - m_child_in = stdin_pipe[1]; - if (fcntl(m_child_in, F_SETFL, O_NONBLOCK) == -1) - { - set_error(errno); - result = false; - } - } - if (connect_stdout) - { - ::close(stdout_pipe[1]); - m_child_out = stdout_pipe[0]; - if (fcntl(m_child_out, F_SETFL, O_NONBLOCK) == -1) - { - set_error(errno); - result = false; - } - } - if (connect_stderr) - { - ::close(stderr_pipe[1]); - m_child_err = stderr_pipe[0]; - if (fcntl(m_child_err, F_SETFL, O_NONBLOCK) == -1) - { - set_error(errno); - result = false; - } - } - } - break; - } - return result; - } - -#endif - - bool async_subprocess::spawn(const std::string& command_line, - bool connect_stdin, bool connect_stdout, bool connect_stderr) - { - arg_vector arguments = command_line; - if (arguments.size() == 0) return false; - std::string path = path_lookup(arguments.argv0()); - if (path.empty()) return false; - return spawn(path, arguments, connect_stdin, connect_stdout, connect_stderr); - } - - bool async_subprocess::callback(void) - { - return true; - } - -#ifdef MSWINDOWS - - bool async_subprocess::tick(void) - { - bool result = true; - if (!callback()) - kill(); - DWORD exit_status = 0; - if (!GetExitCodeProcess(m_pid.hProcess, &exit_status)) - { - set_error(GetLastError()); - result = false; - } - else if (exit_status != STILL_ACTIVE) - { - CloseHandle(m_pid.hThread); - CloseHandle(m_pid.hProcess); - CloseHandle(m_job); - m_pid.hProcess = 0; - result = false; - } - m_status = (int)exit_status; - return result; - } - -#else - - bool async_subprocess::tick(void) - { - bool result = true; - if (!callback()) - kill(); - int wait_status = 0; - int wait_ret_val = waitpid(m_pid, &wait_status, WNOHANG); - if (wait_ret_val == -1 && errno != EINTR) - { - set_error(errno); - result = false; - } - else if (wait_ret_val != 0) - { - // the only states that indicate a terminated child are WIFSIGNALLED and WIFEXITED - if (WIFSIGNALED(wait_status)) - { - // set_error(errno); - m_status = WTERMSIG(wait_status); - result = false; - } - else if (WIFEXITED(wait_status)) - { - // child has exited - m_status = WEXITSTATUS(wait_status); - result = false; - } - } - if (!result) - m_pid = -1; - return result; - } - -#endif - -#ifdef MSWINDOWS - - bool async_subprocess::kill(void) - { - if (!m_pid.hProcess) return false; - close_stdin(); - close_stdout(); - close_stderr(); - if (!TerminateJobObject(m_job, (UINT)-1)) - { - set_error(GetLastError()); - return false; - } - return true; - } - -#else - - bool async_subprocess::kill(void) - { - if (m_pid == -1) return false; - close_stdin(); - close_stdout(); - close_stderr(); - if (::kill(m_pid, SIGINT) == -1) - { - set_error(errno); - return false; - } - return true; - } - -#endif - -#ifdef MSWINDOWS - - int async_subprocess::write_stdin (std::string& buffer) - { - if (m_child_in == 0) return -1; - // there doesn't seem to be a way of doing non-blocking writes under Windoze - DWORD bytes = 0; - if (!WriteFile(m_child_in, buffer.c_str(), (DWORD)buffer.size(), &bytes, 0)) - { - set_error(GetLastError()); - close_stdin(); - return -1; - } - // now discard that part of the buffer that was written - if (bytes > 0) - buffer.erase(0, bytes); - return (int)bytes; - } - -#else - - int async_subprocess::write_stdin (std::string& buffer) - { - if (m_child_in == -1) return -1; - // relies on the pipe being non-blocking - // This does block under Windoze - int bytes = write(m_child_in, buffer.c_str(), buffer.size()); - if (bytes == -1 && errno == EAGAIN) - { - // not ready - return 0; - } - if (bytes == -1) - { - // error on write - close the pipe and give up - set_error(errno); - close_stdin(); - return -1; - } - // successful write - // now discard that part of the buffer that was written - if (bytes > 0) - buffer.erase(0, bytes); - return bytes; - } - -#endif - -#ifdef MSWINDOWS - - int async_subprocess::read_stdout (std::string& buffer) - { - if (m_child_out == 0) return -1; - // peek at the buffer to see how much data there is in the first place - DWORD buffer_size = 0; - if (!PeekNamedPipe(m_child_out, 0, 0, 0, &buffer_size, 0)) - { - if (GetLastError() != ERROR_BROKEN_PIPE) - set_error(GetLastError()); - close_stdout(); - return -1; - } - if (buffer_size == 0) return 0; - DWORD bytes = 0; - char* tmp = new char[buffer_size]; - if (!ReadFile(m_child_out, tmp, buffer_size, &bytes, 0)) - { - set_error(GetLastError()); - close_stdout(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stdout(); - delete[] tmp; - return -1; - } - buffer.append(tmp, bytes); - delete[] tmp; - return (int)bytes; - } - -#else - - int async_subprocess::read_stdout (std::string& buffer) - { - if (m_child_out == -1) return -1; - // rely on the pipe being non-blocking - int buffer_size = 256; - char* tmp = new char[buffer_size]; - int bytes = read(m_child_out, tmp, buffer_size); - if (bytes == -1 && errno == EAGAIN) - { - // not ready - delete[] tmp; - return 0; - } - if (bytes == -1) - { - // error - set_error(errno); - close_stdout(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stdout(); - delete[] tmp; - return -1; - } - // successful read - buffer.append(tmp, bytes); - delete[] tmp; - return bytes; - } - -#endif - -#ifdef MSWINDOWS - - int async_subprocess::read_stderr (std::string& buffer) - { - if (m_child_err == 0) return -1; - // peek at the buffer to see how much data there is in the first place - DWORD buffer_size = 0; - if (!PeekNamedPipe(m_child_err, 0, 0, 0, &buffer_size, 0)) - { - if (GetLastError() != ERROR_BROKEN_PIPE) - set_error(GetLastError()); - close_stderr(); - return -1; - } - if (buffer_size == 0) return 0; - DWORD bytes = 0; - char* tmp = new char[buffer_size]; - if (!ReadFile(m_child_err, tmp, buffer_size, &bytes, 0)) - { - set_error(GetLastError()); - close_stderr(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stderr(); - delete[] tmp; - return -1; - } - buffer.append(tmp, bytes); - delete[] tmp; - return (int)bytes; - } - -#else - - int async_subprocess::read_stderr (std::string& buffer) - { - if (m_child_err == -1) return -1; - // rely on the pipe being non-blocking - int buffer_size = 256; - char* tmp = new char[buffer_size]; - int bytes = read(m_child_err, tmp, buffer_size); - if (bytes == -1 && errno == EAGAIN) - { - // not ready - delete[] tmp; - return 0; - } - if (bytes == -1) - { - // error - set_error(errno); - close_stderr(); - delete[] tmp; - return -1; - } - if (bytes == 0) - { - // EOF - close_stderr(); - delete[] tmp; - return -1; - } - // successful read - buffer.append(tmp, bytes); - delete[] tmp; - return bytes; - } - -#endif - -#ifdef MSWINDOWS - - void async_subprocess::close_stdin (void) - { - if (m_child_in) - { - CloseHandle(m_child_in); - m_child_in = 0; - } - } - -#else - - void async_subprocess::close_stdin (void) - { - if (m_child_in != -1) - { - ::close(m_child_in); - m_child_in = -1; - } - } - -#endif - -#ifdef MSWINDOWS - - void async_subprocess::close_stdout (void) - { - if (m_child_out) - { - CloseHandle(m_child_out); - m_child_out = 0; - } - } - -#else - - void async_subprocess::close_stdout (void) - { - if (m_child_out != -1) - { - ::close(m_child_out); - m_child_out = -1; - } - } - -#endif - -#ifdef MSWINDOWS - - void async_subprocess::close_stderr (void) - { - if (m_child_err) - { - CloseHandle(m_child_err); - m_child_err = 0; - } - } - -#else - - void async_subprocess::close_stderr (void) - { - if (m_child_err != -1) - { - ::close(m_child_err); - m_child_err = -1; - } - } - -#endif - - bool async_subprocess::error(void) const - { - return m_err != 0; - } - - int async_subprocess::error_number(void) const - { - return m_err; - } - -#ifdef MSWINDOWS - - std::string async_subprocess::error_text(void) const - { - if (m_err == 0) return std::string(); - char* message; - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, - 0, - m_err, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR)&message, - 0,0); - std::string result = message; - LocalFree(message); - // the error message is for some perverse reason newline terminated - remove this - if (result[result.size()-1] == '\n') - result.erase(result.end()-1); - if (result[result.size()-1] == '\r') - result.erase(result.end()-1); - return result; - } - -#else - - std::string async_subprocess::error_text(void) const - { - if (m_err == 0) return std::string(); - char* text = strerror(m_err); - if (text) return std::string(text); - return "error number " + dformat("%d",m_err); - } - -#endif - - int async_subprocess::exit_status(void) const - { - return m_status; - } - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// + +// Bug fix by Alistair Low: kill on Windows now kills grandchild processes as +// well as the child process. This is done using jobs - which has to be +// enabled by stating that the version of Windows is at least 5.0 +#if defined(_WIN32) || defined(_WIN32_WCE) +#define _WIN32_WINNT 0x0500 +#endif + +#include "subprocesses.hpp" +#include "file_system.hpp" +#include "dprintf.hpp" +#include +#include +#include + +#ifdef MSWINDOWS +#ifdef __BORLANDC__ +// missing declaration in Borland headers +LPTCH WINAPI GetEnvironmentStringsA(void); +#endif +#else +extern char** environ; +#include +#include +#include +#include +#include +#endif + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // argument-vector related stuff + + static void skip_white (const std::string& command, unsigned& i) + { + while(i < command.size() && isspace(command[i])) + i++; + } + + // get_argument is the main function for breaking a string down into separate command arguments + // it mimics the way shells break down a command into an argv[] and unescapes the escaped characters on the way + + static std::string get_argument (const std::string& command, unsigned& i) + { + std::string result; +#ifdef MSWINDOWS + + // as far as I know, there is only double-quoting and no escape character in DOS + // so, how do you include a double-quote in an argument??? + + bool dquote = false; + for ( ; i < command.size(); i++) + { + char ch = command[i]; + if (!dquote && isspace(ch)) break; + if (dquote) + { + if (ch == '\"') + dquote = false; + else + result += ch; + } + else if (ch == '\"') + dquote = true; + else + result += ch; + } +#else + bool squote = false; + bool dquote = false; + bool escaped = false; + for ( ; i < command.size(); i++) + { + char ch = command[i]; + if (!squote && !dquote && !escaped && isspace(ch)) break; + if (escaped) + { + result += ch; + escaped = false; + } + else if (squote) + { + if (ch == '\'') + squote = false; + else + result += ch; + } + else if (ch == '\\') + escaped = true; + else if (dquote) + { + if (ch == '\"') + dquote = false; + else + result += ch; + } + else if (ch == '\'') + squote = true; + else if (ch == '\"') + dquote = true; + else + result += ch; + } +#endif + + return result; + } + + + // this function performs the reverse of the above on a single argument + // it escapes special characters and quotes the argument if necessary ready for shell interpretation + + static std::string make_argument (const std::string& arg) + { + std::string result; + bool needs_quotes = false; + + for (unsigned i = 0; i < arg.size(); i++) + { + switch (arg[i]) + { + // set of characters requiring escapes +#ifdef MSWINDOWS +#else + case '\\': case '\'': case '\"': case '`': case '(': case ')': + case '&': case '|': case '<': case '>': case '*': case '?': case '!': + result += '\\'; + result += arg[i]; + break; +#endif + // set of whitespace characters that force quoting + case ' ': + result += arg[i]; + needs_quotes = true; + break; + default: + result += arg[i]; + break; + } + } + + if (needs_quotes) + { + result.insert(result.begin(), '"'); + result += '"'; + } + return result; + } + + static char* copy_string (const char* str) + { + char* result = new char[strlen(str)+1]; + strcpy(result,str); + return result; + } + + //////////////////////////////////////////////////////////////////////////////// + + arg_vector::arg_vector (void) + { + m_argv = 0; + } + + arg_vector::arg_vector (const arg_vector& a) + { + m_argv = 0; + *this = a; + } + + arg_vector::arg_vector (char** a) + { + m_argv = 0; + *this = a; + } + + arg_vector::arg_vector (const std::string& command) + { + m_argv = 0; + *this = command; + } + + arg_vector::arg_vector (const char* command) + { + m_argv = 0; + *this = command; + } + + arg_vector::~arg_vector (void) + { + clear(); + } + + arg_vector& arg_vector::operator = (const arg_vector& a) + { + return *this = a.m_argv; + } + + arg_vector& arg_vector::operator = (char** argv) + { + clear(); + for (unsigned i = 0; argv[i]; i++) + operator += (argv[i]); + return *this; + } + + arg_vector& arg_vector::operator = (const std::string& command) + { + clear(); + for (unsigned i = 0; i < command.size(); ) + { + std::string argument = get_argument(command, i); + operator += (argument); + skip_white(command, i); + } + return *this; + } + + arg_vector& arg_vector::operator = (const char* command) + { + return operator = (std::string(command)); + } + + arg_vector& arg_vector::operator += (const std::string& str) + { + insert(size(), str); + return *this; + } + + arg_vector& arg_vector::operator -= (const std::string& str) + { + insert(0, str); + return *this; + } + + void arg_vector::insert (unsigned index, const std::string& str) throw(std::out_of_range) + { + if (index > size()) throw std::out_of_range("arg_vector::insert"); + // copy up to but not including index, then add the new argument, then copy the rest + char** new_argv = new char*[size()+2]; + unsigned i = 0; + for ( ; i < index; i++) + new_argv[i] = copy_string(m_argv[i]); + new_argv[index] = copy_string(str.c_str()); + for ( ; i < size(); i++) + new_argv[i+1] = copy_string(m_argv[i]); + new_argv[i+1] = 0; + clear(); + m_argv = new_argv; + } + + void arg_vector::clear (unsigned index) throw(std::out_of_range) + { + if (index >= size()) throw std::out_of_range("arg_vector::clear"); + // copy up to index, skip it, then copy the rest + char** new_argv = new char*[size()]; + unsigned i = 0; + for ( ; i < index; i++) + new_argv[i] = copy_string(m_argv[i]); + i++; + for ( ; i < size(); i++) + new_argv[i-1] = copy_string(m_argv[i]); + new_argv[i-1] = 0; + clear(); + m_argv = new_argv; + } + + void arg_vector::clear(void) + { + if (m_argv) + { + for (unsigned i = 0; m_argv[i]; i++) + delete[] m_argv[i]; + delete[] m_argv; + m_argv = 0; + } + } + + unsigned arg_vector::size (void) const + { + unsigned i = 0; + if (m_argv) + while (m_argv[i]) + i++; + return i; + } + + arg_vector::operator char** (void) const + { + return m_argv; + } + + char** arg_vector::argv (void) const + { + return m_argv; + } + + char* arg_vector::operator [] (unsigned index) const throw(std::out_of_range) + { + if (index >= size()) throw std::out_of_range("arg_vector::operator[]"); + return m_argv[index]; + } + + char* arg_vector::argv0 (void) const throw(std::out_of_range) + { + return operator [] (0); + } + + std::string arg_vector::image (void) const + { + std::string result; + for (unsigned i = 0; i < size(); i++) + { + if (i) result += ' '; + result += make_argument(m_argv[i]); + } + return result; + } + + //////////////////////////////////////////////////////////////////////////////// + // environment-vector + + // Windoze environment is a single string containing null-terminated + // name=value strings and the whole terminated by a null + + // Unix environment is a null-terminated vector of pointers to null-terminated strings + + //////////////////////////////////////////////////////////////////////////////// + // platform specifics + +#ifdef MSWINDOWS + // Windows utilities + + // Windows environment variables are case-insensitive and I do comparisons by converting to lowercase + static std::string lowercase(const std::string& val) + { + std::string text = val; + for (unsigned i = 0; i < text.size(); i++) + text[i] = tolower(text[i]); + return text; + } + + static unsigned envp_size(const char* envp) + { + unsigned size = 0; + while (envp[size] || (size > 0 && envp[size-1])) size++; + size++; + return size; + } + + static void envp_extract(std::string& name, std::string& value, const char* envp, unsigned& envi) + { + name.erase(); + value.erase(); + if (!envp[envi]) return; + // some special variables start with '=' so ensure at least one character in the name + name += envp[envi++]; + while(envp[envi] != '=') + name += envp[envi++]; + envi++; + while(envp[envi]) + value += envp[envi++]; + envi++; + } + + static void envp_append(const std::string& name, const std::string& value, char* envp, unsigned& envi) + { + for (unsigned i = 0; i < name.size(); i++) + envp[envi++] = name[i]; + envp[envi++] = '='; + for (unsigned j = 0; j < value.size(); j++) + envp[envi++] = value[j]; + envp[envi++] = '\0'; + envp[envi] = '\0'; + } + + static char* envp_copy(const char* envp) + { + unsigned size = envp_size(envp); + char* result = new char[size]; + result[0] = '\0'; + unsigned i = 0; + unsigned j = 0; + while(envp[i]) + { + std::string name; + std::string value; + envp_extract(name, value, envp, i); + envp_append(name, value, result, j); + } + return result; + } + + static void envp_clear(char*& envp) + { + if (envp) + { + delete[] envp; + envp = 0; + } + } + + static bool envp_equal(const std::string& left, const std::string& right) + { + return lowercase(left) == lowercase(right); + } + + static bool envp_less(const std::string& left, const std::string& right) + { + return lowercase(left) < lowercase(right); + } + +#else + // Unix utilities + + static unsigned envp_size(char* const* envp) + { + unsigned size = 0; + while(envp[size]) size++; + size++; + return size; + } + + static void envp_extract(std::string& name, std::string& value, char* const* envp, unsigned& envi) + { + name = ""; + value = ""; + if (!envp[envi]) return; + unsigned i = 0; + while(envp[envi][i] != '=') + name += envp[envi][i++]; + i++; + while(envp[envi][i]) + value += envp[envi][i++]; + envi++; + } + + static void envp_append(const std::string& name, const std::string& value, char** envp, unsigned& envi) + { + std::string entry = name + "=" + value; + envp[envi] = copy_string(entry.c_str()); + envi++; + envp[envi] = 0; + } + + static char** envp_copy(char* const* envp) + { + unsigned size = envp_size(envp); + char** result = new char*[size]; + unsigned i = 0; + unsigned j = 0; + while(envp[i]) + { + std::string name; + std::string value; + envp_extract(name, value, envp, i); + envp_append(name, value, result, j); + } + return result; + } + + static void envp_clear(char**& envp) + { + if (envp) + { + for (unsigned i = 0; envp[i]; i++) + delete[] envp[i]; + delete[] envp; + envp = 0; + } + } + + static bool envp_equal(const std::string& left, const std::string& right) + { + return left == right; + } + + static bool envp_less(const std::string& left, const std::string& right) + { + return left < right; + } + +#endif + //////////////////////////////////////////////////////////////////////////////// + + env_vector::env_vector(void) + { +#ifdef MSWINDOWS + char* env = (char*)GetEnvironmentStringsA(); + m_env = envp_copy(env); + FreeEnvironmentStringsA(env); +#else + m_env = envp_copy(::environ); +#endif + } + + env_vector::env_vector (const env_vector& a) + { + m_env = 0; + *this = a; + } + + env_vector::~env_vector (void) + { + clear(); + } + + env_vector& env_vector::operator = (const env_vector& a) + { + clear(); + m_env = envp_copy(a.m_env); + return *this; + } + + void env_vector::clear(void) + { + envp_clear(m_env); + } + + void env_vector::add(const std::string& name, const std::string& value) + { + // the trick is to add the value in alphabetic order + // this is done by copying the existing environment string to a new + // string, inserting the new value when a name greater than it is found + unsigned size = envp_size(m_env); +#ifdef MSWINDOWS + unsigned new_size = size + name.size() + value.size() + 2; + char* new_v = new char[new_size]; + new_v[0] = '\0'; +#else + unsigned new_size = size + 1; + char** new_v = new char*[new_size]; + new_v[0] = 0; +#endif + // now extract each name=value pair and check the ordering + bool added = false; + unsigned i = 0; + unsigned j = 0; + while(m_env[i]) + { + std::string current_name; + std::string current_value; + envp_extract(current_name, current_value, m_env, i); + if (envp_equal(name,current_name)) + { + // replace an existing value + envp_append(name, value, new_v, j); + } + else if (!added && envp_less(name,current_name)) + { + // add the new value first, then the existing one + envp_append(name, value, new_v, j); + envp_append(current_name, current_value, new_v, j); + added = true; + } + else + { + // just add the existing value + envp_append(current_name, current_value, new_v, j); + } + } + if (!added) + envp_append(name, value, new_v, j); + envp_clear(m_env); + m_env = new_v; + } + + + bool env_vector::remove (const std::string& name) + { + bool result = false; + // this is done by copying the existing environment string to a new string, but excluding the specified name + unsigned size = envp_size(m_env); +#ifdef MSWINDOWS + char* new_v = new char[size]; + new_v[0] = '\0'; +#else + char** new_v = new char*[size]; + new_v[0] = 0; +#endif + unsigned i = 0; + unsigned j = 0; + while(m_env[i]) + { + std::string current_name; + std::string current_value; + envp_extract(current_name, current_value, m_env, i); + if (envp_equal(name,current_name)) + result = true; + else + envp_append(current_name, current_value, new_v, j); + } + envp_clear(m_env); + m_env = new_v; + return result; + } + + bool env_vector::present (const std::string& name) const + { + unsigned i = 0; + while(m_env[i]) + { + std::string current_name; + std::string current_value; + envp_extract(current_name, current_value, m_env, i); + if (envp_equal(name,current_name)) + return true; + } + return false; + } + + std::string env_vector::operator [] (const std::string& name) const + { + return get(name); + } + + std::string env_vector::get (const std::string& name) const + { + unsigned i = 0; + while(m_env[i]) + { + std::string current_name; + std::string current_value; + envp_extract(current_name, current_value, m_env, i); + if (envp_equal(name,current_name)) + return current_value; + } + return std::string(); + } + + unsigned env_vector::size (void) const + { + unsigned i = 0; +#ifdef MSWINDOWS + unsigned offset = 0; + while(m_env[offset]) + { + std::string current_name; + std::string current_value; + envp_extract(current_name, current_value, m_env, offset); + i++; + } +#else + while(m_env[i]) + i++; +#endif + + return i; + } + + std::pair env_vector::operator [] (unsigned index) const throw(std::out_of_range) + { + return get(index); + } + + std::pair env_vector::get (unsigned index) const throw(std::out_of_range) + { + if (index >= size()) throw std::out_of_range("arg_vector::get"); + unsigned j = 0; + for (unsigned i = 0; i < index; i++) + { + std::string current_name; + std::string current_value; + envp_extract(current_name, current_value, m_env, j); + } + std::string name; + std::string value; + envp_extract(name, value, m_env, j); + return std::make_pair(name,value); + } + + ENVIRON_TYPE env_vector::envp (void) const + { + return m_env; + } + + //////////////////////////////////////////////////////////////////////////////// + // Synchronous subprocess + // Win32 implementation mostly cribbed from MSDN examples and then made (much) more readable + // Unix implementation mostly from man pages and bitter experience + //////////////////////////////////////////////////////////////////////////////// + +#ifdef MSWINDOWS + + subprocess::subprocess(void) + { + m_pid.hProcess = 0; + m_job = 0; + m_child_in = 0; + m_child_out = 0; + m_child_err = 0; + m_err = 0; + m_status = 0; + } + +#else + + subprocess::subprocess(void) + { + m_pid = -1; + m_child_in = -1; + m_child_out = -1; + m_child_err = -1; + m_err = 0; + m_status = 0; + } + +#endif + +#ifdef MSWINDOWS + + subprocess::~subprocess(void) + { + if (m_pid.hProcess != 0) + { + close_stdin(); + close_stdout(); + close_stderr(); + kill(); + WaitForSingleObject(m_pid.hProcess, INFINITE); + CloseHandle(m_pid.hThread); + CloseHandle(m_pid.hProcess); + CloseHandle(m_job); + } + } + +#else + + subprocess::~subprocess(void) + { + if (m_pid != -1) + { + close_stdin(); + close_stdout(); + close_stderr(); + kill(); + for (;;) + { + int wait_status = 0; + int wait_ret_val = waitpid(m_pid, &wait_status, 0); + if (wait_ret_val != -1 || errno != EINTR) break; + } + } + } + +#endif + + void subprocess::set_error(int e) + { + m_err = e; + } + + void subprocess::add_variable(const std::string& name, const std::string& value) + { + m_env.add(name, value); + } + + bool subprocess::remove_variable(const std::string& name) + { + return m_env.remove(name); + } + + const env_vector& subprocess::get_variables(void) const + { + return m_env; + } + +#ifdef MSWINDOWS + + bool subprocess::spawn(const std::string& path, const arg_vector& argv, + bool connect_stdin, bool connect_stdout, bool connect_stderr) + { + bool result = true; + // first create the pipes to be used to connect to the child stdin/out/err + // If no pipes requested, then connect to the parent stdin/out/err + // for some reason you have to create a pipe handle, then duplicate it + // This is not well explained in MSDN but seems to work + PIPE_TYPE parent_stdin = 0; + if (!connect_stdin) + parent_stdin = GetStdHandle(STD_INPUT_HANDLE); + else + { + PIPE_TYPE tmp = 0; + SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; + CreatePipe(&parent_stdin, &tmp, &inherit_handles, 0); + DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_in, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); + } + + PIPE_TYPE parent_stdout = 0; + if (!connect_stdout) + parent_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + else + { + PIPE_TYPE tmp = 0; + SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; + CreatePipe(&tmp, &parent_stdout, &inherit_handles, 0); + DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_out, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); + } + + PIPE_TYPE parent_stderr = 0; + if (!connect_stderr) + parent_stderr = GetStdHandle(STD_ERROR_HANDLE); + else + { + PIPE_TYPE tmp = 0; + SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; + CreatePipe(&tmp, &parent_stderr, &inherit_handles, 0); + DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_err, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); + } + + // Now create the subprocess + // The horrible trick of creating a console window and hiding it seems to be required for the pipes to work + // Note that the child will inherit a copy of the pipe handles + STARTUPINFOA startup = {sizeof(STARTUPINFO),0,0,0,0,0,0,0,0,0,0, + STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW,SW_HIDE,0,0, + parent_stdin,parent_stdout,parent_stderr}; + bool created = CreateProcessA(path.c_str(),(char*)argv.image().c_str(),0,0,TRUE,CREATE_SUSPENDED,m_env.envp(),0,&startup,&m_pid) != 0; + // close the parent copy of the pipe handles so that the pipes will be closed when the child releases them + if (connect_stdin) CloseHandle(parent_stdin); + if (connect_stdout) CloseHandle(parent_stdout); + if (connect_stderr) CloseHandle(parent_stderr); + if (!created) + { + set_error(GetLastError()); + close_stdin(); + close_stdout(); + close_stderr(); + result = false; + } + else + { + m_job = CreateJobObject(NULL, NULL); + AssignProcessToJobObject(m_job, m_pid.hProcess); + ResumeThread(m_pid.hThread); + + // The child process is now running so call the user's callback + // The convention is that the callback can return false, in which case this will kill the child (if its still running) + if (!callback()) + { + result = false; + kill(); + } + close_stdin(); + close_stdout(); + close_stderr(); + // wait for the child to finish + // TODO - kill the child if a timeout happens + WaitForSingleObject(m_pid.hProcess, INFINITE); + DWORD exit_status = 0; + if (!GetExitCodeProcess(m_pid.hProcess, &exit_status)) + { + set_error(GetLastError()); + result = false; + } + else if (exit_status != 0) + result = false; + m_status = (int)exit_status; + CloseHandle(m_pid.hThread); + CloseHandle(m_pid.hProcess); + CloseHandle(m_job); + } + m_pid.hProcess = 0; + return result; + } + +#else + + bool subprocess::spawn(const std::string& path, const arg_vector& argv, + bool connect_stdin, bool connect_stdout, bool connect_stderr) + { + bool result = true; + // first create the pipes to be used to connect to the child stdin/out/err + + int stdin_pipe [2] = {-1, -1}; + if (connect_stdin) + if (::pipe(stdin_pipe) != 0) + set_error(errno); + + int stdout_pipe [2] = {-1, -1}; + if (connect_stdout) + if (::pipe(stdout_pipe) != 0) + set_error(errno); + + int stderr_pipe [2] = {-1, -1}; + if (connect_stderr) + if (::pipe(stderr_pipe) != 0) + set_error(errno); + + // now create the subprocess + // In Unix, this is done by forking (creating two copies of the parent), then overwriting the child copy using exec + m_pid = ::fork(); + switch(m_pid) + { + case -1: // failed to fork + set_error(errno); + if (connect_stdin) + { + ::close(stdin_pipe[0]); + ::close(stdin_pipe[1]); + } + if (connect_stdout) + { + ::close(stdout_pipe[0]); + ::close(stdout_pipe[1]); + } + if (connect_stderr) + { + ::close(stderr_pipe[0]); + ::close(stderr_pipe[1]); + } + result = false; + break; + case 0: // in child; + { + // for each pipe, close the end of the duplicated pipe that is being used by the parent + // and connect the child's end of the pipe to the appropriate standard I/O device + if (connect_stdin) + { + ::close(stdin_pipe[1]); + ::dup2(stdin_pipe[0],STDIN_FILENO); + } + if (connect_stdout) + { + ::close(stdout_pipe[0]); + ::dup2(stdout_pipe[1],STDOUT_FILENO); + } + if (connect_stderr) + { + ::close(stderr_pipe[0]); + ::dup2(stderr_pipe[1],STDERR_FILENO); + } + execve(path.c_str(), argv.argv(), m_env.envp()); + // will only ever get here if the exec() failed completely - *must* now exit the child process + // by using errno, the parent has some chance of diagnosing the cause of the problem + exit(errno); + } + break; + default: // in parent + { + // for each pipe, close the end of the duplicated pipe that is being used by the child + // and connect the parent's end of the pipe to the class members so that they are visible to the parent() callback + if (connect_stdin) + { + ::close(stdin_pipe[0]); + m_child_in = stdin_pipe[1]; + } + if (connect_stdout) + { + ::close(stdout_pipe[1]); + m_child_out = stdout_pipe[0]; + } + if (connect_stderr) + { + ::close(stderr_pipe[1]); + m_child_err = stderr_pipe[0]; + } + // call the user's callback + if (!callback()) + { + result = false; + kill(); + } + // close the pipes and wait for the child to finish + // wait exits on a signal which may be the child signalling its exit or may be an interrupt + close_stdin(); + close_stdout(); + close_stderr(); + int wait_status = 0; + for (;;) + { + int wait_ret_val = waitpid(m_pid, &wait_status, 0); + if (wait_ret_val != -1 || errno != EINTR) break; + } + // establish whether an error occurred + if (WIFSIGNALED(wait_status)) + { + // set_error(errno); + m_status = WTERMSIG(wait_status); + result = false; + } + else if (WIFEXITED(wait_status)) + { + m_status = WEXITSTATUS(wait_status); + if (m_status != 0) + result = false; + } + m_pid = -1; + } + break; + } + return result; + } + +#endif + + bool subprocess::spawn(const std::string& command_line, + bool connect_stdin, bool connect_stdout, bool connect_stderr) + { + arg_vector arguments = command_line; + if (arguments.size() == 0) return false; + std::string path = path_lookup(arguments.argv0()); + if (path.empty()) return false; + return spawn(path, arguments, connect_stdin, connect_stdout, connect_stderr); + } + + bool subprocess::callback(void) + { + return true; + } + +#ifdef MSWINDOWS + + bool subprocess::kill (void) + { + if (!m_pid.hProcess) return false; + close_stdin(); + close_stdout(); + close_stderr(); + if (!TerminateJobObject(m_job, (UINT)-1)) + { + set_error(GetLastError()); + return false; + } + return true; + } + +#else + + bool subprocess::kill (void) + { + if (m_pid == -1) return false; + close_stdin(); + close_stdout(); + close_stderr(); + if (::kill(m_pid, SIGINT) == -1) + { + set_error(errno); + return false; + } + return true; + } + +#endif + +#ifdef MSWINDOWS + + int subprocess::write_stdin (std::string& buffer) + { + if (m_child_in == 0) return -1; + // do a blocking write of the whole buffer + DWORD bytes = 0; + if (!WriteFile(m_child_in, buffer.c_str(), (DWORD)buffer.size(), &bytes, 0)) + { + set_error(GetLastError()); + close_stdin(); + return -1; + } + // now discard that part of the buffer that was written + if (bytes > 0) + buffer.erase(0, bytes); + return bytes; + } + +#else + + int subprocess::write_stdin (std::string& buffer) + { + if (m_child_in == -1) return -1; + // do a blocking write of the whole buffer + int bytes = write(m_child_in, buffer.c_str(), buffer.size()); + if (bytes == -1) + { + set_error(errno); + close_stdin(); + return -1; + } + // now discard that part of the buffer that was written + if (bytes > 0) + buffer.erase(0, bytes); + return bytes; + } + +#endif + +#ifdef MSWINDOWS + + int subprocess::read_stdout (std::string& buffer) + { + if (m_child_out == 0) return -1; + DWORD bytes = 0; + DWORD buffer_size = 256; + char* tmp = new char[buffer_size]; + if (!ReadFile(m_child_out, tmp, buffer_size, &bytes, 0)) + { + if (GetLastError() != ERROR_BROKEN_PIPE) + set_error(GetLastError()); + close_stdout(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stdout(); + delete[] tmp; + return -1; + } + buffer.append(tmp, bytes); + delete[] tmp; + return (int)bytes; + } + +#else + + int subprocess::read_stdout (std::string& buffer) + { + if (m_child_out == -1) return -1; + int buffer_size = 256; + char* tmp = new char[buffer_size]; + int bytes = read(m_child_out, tmp, buffer_size); + if (bytes == -1) + { + set_error(errno); + close_stdout(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stdout(); + delete[] tmp; + return -1; + } + buffer.append(tmp, bytes); + delete[] tmp; + return bytes; + } + +#endif + +#ifdef MSWINDOWS + + int subprocess::read_stderr(std::string& buffer) + { + if (m_child_err == 0) return -1; + DWORD bytes = 0; + DWORD buffer_size = 256; + char* tmp = new char[buffer_size]; + if (!ReadFile(m_child_err, tmp, buffer_size, &bytes, 0)) + { + if (GetLastError() != ERROR_BROKEN_PIPE) + set_error(GetLastError()); + close_stderr(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stderr(); + delete[] tmp; + return -1; + } + buffer.append(tmp, bytes); + delete[] tmp; + return (int)bytes; + } + +#else + + int subprocess::read_stderr (std::string& buffer) + { + if (m_child_err == -1) return -1; + int buffer_size = 256; + char* tmp = new char[buffer_size]; + int bytes = read(m_child_err, tmp, buffer_size); + if (bytes == -1) + { + set_error(errno); + close_stderr(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stderr(); + delete[] tmp; + return -1; + } + buffer.append(tmp, bytes); + delete[] tmp; + return bytes; + } + +#endif + +#ifdef MSWINDOWS + + void subprocess::close_stdin (void) + { + if (m_child_in) + { + CloseHandle(m_child_in); + m_child_in = 0; + } + } + +#else + + void subprocess::close_stdin (void) + { + if (m_child_in != -1) + { + ::close(m_child_in); + m_child_in = -1; + } + } + +#endif + +#ifdef MSWINDOWS + + void subprocess::close_stdout (void) + { + if (m_child_out) + { + CloseHandle(m_child_out); + m_child_out = 0; + } + } + +#else + + void subprocess::close_stdout (void) + { + if (m_child_out != -1) + { + ::close(m_child_out); + m_child_out = -1; + } + } + +#endif + +#ifdef MSWINDOWS + + void subprocess::close_stderr (void) + { + if (m_child_err) + { + CloseHandle(m_child_err); + m_child_err = 0; + } + } + +#else + + void subprocess::close_stderr (void) + { + if (m_child_err != -1) + { + ::close(m_child_err); + m_child_err = -1; + } + } + +#endif + + bool subprocess::error(void) const + { + return m_err != 0; + } + + int subprocess::error_number(void) const + { + return m_err; + } + +#ifdef MSWINDOWS + + std::string subprocess::error_text(void) const + { + if (!error()) return std::string(); + char* message; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, + 0, + m_err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&message, + 0,0); + std::string result = message; + LocalFree(message); + // the error message is for some perverse reason newline terminated - remove this + if (result[result.size()-1] == '\n') + result.erase(result.end()-1); + if (result[result.size()-1] == '\r') + result.erase(result.end()-1); + return result; + } + +#else + + std::string subprocess::error_text(void) const + { + if (!error()) return std::string(); + char* text = strerror(m_err); + if (text) return std::string(text); + return "error number " + dformat("%d",m_err); + } + +#endif + + int subprocess::exit_status(void) const + { + return m_status; + } + + //////////////////////////////////////////////////////////////////////////////// + // backtick subprocess and operations + + backtick_subprocess::backtick_subprocess(void) : subprocess() + { + } + + bool backtick_subprocess::callback(void) + { + for (;;) + { + std::string buffer; + int read_size = read_stdout(buffer); + if (read_size < 0) break; + m_text += buffer; + } + return !error(); + } + + bool backtick_subprocess::spawn(const std::string& path, const arg_vector& argv) + { + return subprocess::spawn(path, argv, false, true, false); + } + + bool backtick_subprocess::spawn(const std::string& command_line) + { + return subprocess::spawn(command_line, false, true, false); + } + + std::vector backtick_subprocess::text(void) const + { + std::vector result; + // convert the raw text into a vector of strings, each corresponding to a line + // in the process, strip out platform-specific line-endings + result.push_back(std::string()); + for (unsigned i = 0; i < m_text.size(); i++) + { + // handle any kind of line-ending - Dos, Unix or MacOS + switch(m_text[i]) + { + case '\xd': // carriage-return - optionally followed by linefeed + { + // discard optional following linefeed + if ((i+1 < m_text.size()) && (m_text[i+1] == '\xa')) + i++; + // add a new line to the end of the vector + result.push_back(std::string()); + break; + } + case '\xa': // linefeed + { + // add a new line to the end of the vector + result.push_back(std::string()); + break; + } + default: + { + result.back() += m_text[i]; + break; + } + } + } + // tidy up - if the last line ended with a newline, the vector will end with an empty string - discard this + if ((result.size()) > 0 && result.back().empty()) + result.erase(result.end()-1); + return result; + } + + std::vector backtick(const std::string& path, const arg_vector& argv) + { + backtick_subprocess sub; + sub.spawn(path, argv); + return sub.text(); + } + + std::vector backtick(const std::string& command_line) + { + backtick_subprocess sub; + sub.spawn(command_line); + return sub.text(); + } + + //////////////////////////////////////////////////////////////////////////////// + // Asynchronous subprocess + +#ifdef MSWINDOWS + + async_subprocess::async_subprocess(void) + { + m_pid.hProcess = 0; + m_job = 0; + m_child_in = 0; + m_child_out = 0; + m_child_err = 0; + m_err = 0; + m_status = 0; + } + +#else + + async_subprocess::async_subprocess(void) + { + m_pid = -1; + m_child_in = -1; + m_child_out = -1; + m_child_err = -1; + m_err = 0; + m_status = 0; + } + +#endif + +#ifdef MSWINDOWS + + async_subprocess::~async_subprocess(void) + { + if (m_pid.hProcess != 0) + { + close_stdin(); + close_stdout(); + close_stderr(); + kill(); + WaitForSingleObject(m_pid.hProcess, INFINITE); + CloseHandle(m_pid.hThread); + CloseHandle(m_pid.hProcess); + CloseHandle(m_job); + } + } + +#else + + async_subprocess::~async_subprocess(void) + { + if (m_pid != -1) + { + close_stdin(); + close_stdout(); + close_stderr(); + kill(); + for (;;) + { + int wait_status = 0; + int wait_ret_val = waitpid(m_pid, &wait_status, 0); + if (wait_ret_val != -1 || errno != EINTR) break; + } + } + } + +#endif + + void async_subprocess::set_error(int e) + { + m_err = e; + } + + void async_subprocess::add_variable(const std::string& name, const std::string& value) + { + m_env.add(name, value); + } + + bool async_subprocess::remove_variable(const std::string& name) + { + return m_env.remove(name); + } + + const env_vector& async_subprocess::get_variables(void) const + { + return m_env; + } + +#ifdef MSWINDOWS + + bool async_subprocess::spawn(const std::string& path, const arg_vector& argv, + bool connect_stdin, bool connect_stdout, bool connect_stderr) + { + bool result = true; + // first create the pipes to be used to connect to the child stdin/out/err + // If no pipes requested, then connect to the parent stdin/out/err + // for some reason you have to create a pipe handle, then duplicate it + // This is not well explained in MSDN but seems to work + PIPE_TYPE parent_stdin = 0; + if (!connect_stdin) + parent_stdin = GetStdHandle(STD_INPUT_HANDLE); + else + { + PIPE_TYPE tmp = 0; + SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; + CreatePipe(&parent_stdin, &tmp, &inherit_handles, 0); + DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_in, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); + } + + PIPE_TYPE parent_stdout = 0; + if (!connect_stdout) + parent_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + else + { + PIPE_TYPE tmp = 0; + SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; + CreatePipe(&tmp, &parent_stdout, &inherit_handles, 0); + DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_out, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); + } + + PIPE_TYPE parent_stderr = 0; + if (!connect_stderr) + parent_stderr = GetStdHandle(STD_ERROR_HANDLE); + else + { + PIPE_TYPE tmp = 0; + SECURITY_ATTRIBUTES inherit_handles = {sizeof(SECURITY_ATTRIBUTES), 0, TRUE}; + CreatePipe(&tmp, &parent_stderr, &inherit_handles, 0); + DuplicateHandle(GetCurrentProcess(), tmp, GetCurrentProcess(), &m_child_err, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); + } + + // Now create the subprocess + // The horrible trick of creating a console window and hiding it seems to be required for the pipes to work + // Note that the child will inherit a copy of the pipe handles + STARTUPINFOA startup = {sizeof(STARTUPINFO),0,0,0,0,0,0,0,0,0,0, + STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW,SW_HIDE,0,0, + parent_stdin,parent_stdout,parent_stderr}; + bool created = CreateProcessA(path.c_str(),(char*)argv.image().c_str(),0,0,TRUE,CREATE_SUSPENDED,m_env.envp(),0,&startup,&m_pid) != 0; + // close the parent copy of the pipe handles so that the pipes will be closed when the child releases them + if (connect_stdin) CloseHandle(parent_stdin); + if (connect_stdout) CloseHandle(parent_stdout); + if (connect_stderr) CloseHandle(parent_stderr); + if (!created) + { + set_error(GetLastError()); + close_stdin(); + close_stdout(); + close_stderr(); + result = false; + } + else + { + m_job = CreateJobObject(NULL, NULL); + AssignProcessToJobObject(m_job, m_pid.hProcess); + ResumeThread(m_pid.hThread); + } + return result; + } + +#else + + bool async_subprocess::spawn(const std::string& path, const arg_vector& argv, + bool connect_stdin, bool connect_stdout, bool connect_stderr) + { + bool result = true; + // first create the pipes to be used to connect to the child stdin/out/err + + int stdin_pipe [2] = {-1, -1}; + if (connect_stdin) + if (::pipe(stdin_pipe) != 0) + set_error(errno); + + int stdout_pipe [2] = {-1, -1}; + if (connect_stdout) + if (::pipe(stdout_pipe) != 0) + set_error(errno); + + int stderr_pipe [2] = {-1, -1}; + if (connect_stderr) + if (::pipe(stderr_pipe) != 0) + set_error(errno); + + // now create the subprocess + // In Unix, this is done by forking (creating two copies of the parent), then overwriting the child copy using exec + m_pid = ::fork(); + switch(m_pid) + { + case -1: // failed to fork + set_error(errno); + if (connect_stdin) + { + ::close(stdin_pipe[0]); + ::close(stdin_pipe[1]); + } + if (connect_stdout) + { + ::close(stdout_pipe[0]); + ::close(stdout_pipe[1]); + } + if (connect_stderr) + { + ::close(stderr_pipe[0]); + ::close(stderr_pipe[1]); + } + result = false; + break; + case 0: // in child; + { + // for each pipe, close the end of the duplicated pipe that is being used by the parent + // and connect the child's end of the pipe to the appropriate standard I/O device + if (connect_stdin) + { + ::close(stdin_pipe[1]); + ::dup2(stdin_pipe[0],STDIN_FILENO); + } + if (connect_stdout) + { + ::close(stdout_pipe[0]); + ::dup2(stdout_pipe[1],STDOUT_FILENO); + } + if (connect_stderr) + { + ::close(stderr_pipe[0]); + ::dup2(stderr_pipe[1],STDERR_FILENO); + } + ::execve(path.c_str(), argv.argv(), m_env.envp()); + // will only ever get here if the exec() failed completely - *must* now exit the child process + // by using errno, the parent has some chance of diagnosing the cause of the problem + ::exit(errno); + } + break; + default: // in parent + { + // for each pipe, close the end of the duplicated pipe that is being used by the child + // and connect the parent's end of the pipe to the class members so that they are visible to the parent() callback + if (connect_stdin) + { + ::close(stdin_pipe[0]); + m_child_in = stdin_pipe[1]; + if (fcntl(m_child_in, F_SETFL, O_NONBLOCK) == -1) + { + set_error(errno); + result = false; + } + } + if (connect_stdout) + { + ::close(stdout_pipe[1]); + m_child_out = stdout_pipe[0]; + if (fcntl(m_child_out, F_SETFL, O_NONBLOCK) == -1) + { + set_error(errno); + result = false; + } + } + if (connect_stderr) + { + ::close(stderr_pipe[1]); + m_child_err = stderr_pipe[0]; + if (fcntl(m_child_err, F_SETFL, O_NONBLOCK) == -1) + { + set_error(errno); + result = false; + } + } + } + break; + } + return result; + } + +#endif + + bool async_subprocess::spawn(const std::string& command_line, + bool connect_stdin, bool connect_stdout, bool connect_stderr) + { + arg_vector arguments = command_line; + if (arguments.size() == 0) return false; + std::string path = path_lookup(arguments.argv0()); + if (path.empty()) return false; + return spawn(path, arguments, connect_stdin, connect_stdout, connect_stderr); + } + + bool async_subprocess::callback(void) + { + return true; + } + +#ifdef MSWINDOWS + + bool async_subprocess::tick(void) + { + bool result = true; + if (!callback()) + kill(); + DWORD exit_status = 0; + if (!GetExitCodeProcess(m_pid.hProcess, &exit_status)) + { + set_error(GetLastError()); + result = false; + } + else if (exit_status != STILL_ACTIVE) + { + CloseHandle(m_pid.hThread); + CloseHandle(m_pid.hProcess); + CloseHandle(m_job); + m_pid.hProcess = 0; + result = false; + } + m_status = (int)exit_status; + return result; + } + +#else + + bool async_subprocess::tick(void) + { + bool result = true; + if (!callback()) + kill(); + int wait_status = 0; + int wait_ret_val = waitpid(m_pid, &wait_status, WNOHANG); + if (wait_ret_val == -1 && errno != EINTR) + { + set_error(errno); + result = false; + } + else if (wait_ret_val != 0) + { + // the only states that indicate a terminated child are WIFSIGNALLED and WIFEXITED + if (WIFSIGNALED(wait_status)) + { + // set_error(errno); + m_status = WTERMSIG(wait_status); + result = false; + } + else if (WIFEXITED(wait_status)) + { + // child has exited + m_status = WEXITSTATUS(wait_status); + result = false; + } + } + if (!result) + m_pid = -1; + return result; + } + +#endif + +#ifdef MSWINDOWS + + bool async_subprocess::kill(void) + { + if (!m_pid.hProcess) return false; + close_stdin(); + close_stdout(); + close_stderr(); + if (!TerminateJobObject(m_job, (UINT)-1)) + { + set_error(GetLastError()); + return false; + } + return true; + } + +#else + + bool async_subprocess::kill(void) + { + if (m_pid == -1) return false; + close_stdin(); + close_stdout(); + close_stderr(); + if (::kill(m_pid, SIGINT) == -1) + { + set_error(errno); + return false; + } + return true; + } + +#endif + +#ifdef MSWINDOWS + + int async_subprocess::write_stdin (std::string& buffer) + { + if (m_child_in == 0) return -1; + // there doesn't seem to be a way of doing non-blocking writes under Windoze + DWORD bytes = 0; + if (!WriteFile(m_child_in, buffer.c_str(), (DWORD)buffer.size(), &bytes, 0)) + { + set_error(GetLastError()); + close_stdin(); + return -1; + } + // now discard that part of the buffer that was written + if (bytes > 0) + buffer.erase(0, bytes); + return (int)bytes; + } + +#else + + int async_subprocess::write_stdin (std::string& buffer) + { + if (m_child_in == -1) return -1; + // relies on the pipe being non-blocking + // This does block under Windoze + int bytes = write(m_child_in, buffer.c_str(), buffer.size()); + if (bytes == -1 && errno == EAGAIN) + { + // not ready + return 0; + } + if (bytes == -1) + { + // error on write - close the pipe and give up + set_error(errno); + close_stdin(); + return -1; + } + // successful write + // now discard that part of the buffer that was written + if (bytes > 0) + buffer.erase(0, bytes); + return bytes; + } + +#endif + +#ifdef MSWINDOWS + + int async_subprocess::read_stdout (std::string& buffer) + { + if (m_child_out == 0) return -1; + // peek at the buffer to see how much data there is in the first place + DWORD buffer_size = 0; + if (!PeekNamedPipe(m_child_out, 0, 0, 0, &buffer_size, 0)) + { + if (GetLastError() != ERROR_BROKEN_PIPE) + set_error(GetLastError()); + close_stdout(); + return -1; + } + if (buffer_size == 0) return 0; + DWORD bytes = 0; + char* tmp = new char[buffer_size]; + if (!ReadFile(m_child_out, tmp, buffer_size, &bytes, 0)) + { + set_error(GetLastError()); + close_stdout(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stdout(); + delete[] tmp; + return -1; + } + buffer.append(tmp, bytes); + delete[] tmp; + return (int)bytes; + } + +#else + + int async_subprocess::read_stdout (std::string& buffer) + { + if (m_child_out == -1) return -1; + // rely on the pipe being non-blocking + int buffer_size = 256; + char* tmp = new char[buffer_size]; + int bytes = read(m_child_out, tmp, buffer_size); + if (bytes == -1 && errno == EAGAIN) + { + // not ready + delete[] tmp; + return 0; + } + if (bytes == -1) + { + // error + set_error(errno); + close_stdout(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stdout(); + delete[] tmp; + return -1; + } + // successful read + buffer.append(tmp, bytes); + delete[] tmp; + return bytes; + } + +#endif + +#ifdef MSWINDOWS + + int async_subprocess::read_stderr (std::string& buffer) + { + if (m_child_err == 0) return -1; + // peek at the buffer to see how much data there is in the first place + DWORD buffer_size = 0; + if (!PeekNamedPipe(m_child_err, 0, 0, 0, &buffer_size, 0)) + { + if (GetLastError() != ERROR_BROKEN_PIPE) + set_error(GetLastError()); + close_stderr(); + return -1; + } + if (buffer_size == 0) return 0; + DWORD bytes = 0; + char* tmp = new char[buffer_size]; + if (!ReadFile(m_child_err, tmp, buffer_size, &bytes, 0)) + { + set_error(GetLastError()); + close_stderr(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stderr(); + delete[] tmp; + return -1; + } + buffer.append(tmp, bytes); + delete[] tmp; + return (int)bytes; + } + +#else + + int async_subprocess::read_stderr (std::string& buffer) + { + if (m_child_err == -1) return -1; + // rely on the pipe being non-blocking + int buffer_size = 256; + char* tmp = new char[buffer_size]; + int bytes = read(m_child_err, tmp, buffer_size); + if (bytes == -1 && errno == EAGAIN) + { + // not ready + delete[] tmp; + return 0; + } + if (bytes == -1) + { + // error + set_error(errno); + close_stderr(); + delete[] tmp; + return -1; + } + if (bytes == 0) + { + // EOF + close_stderr(); + delete[] tmp; + return -1; + } + // successful read + buffer.append(tmp, bytes); + delete[] tmp; + return bytes; + } + +#endif + +#ifdef MSWINDOWS + + void async_subprocess::close_stdin (void) + { + if (m_child_in) + { + CloseHandle(m_child_in); + m_child_in = 0; + } + } + +#else + + void async_subprocess::close_stdin (void) + { + if (m_child_in != -1) + { + ::close(m_child_in); + m_child_in = -1; + } + } + +#endif + +#ifdef MSWINDOWS + + void async_subprocess::close_stdout (void) + { + if (m_child_out) + { + CloseHandle(m_child_out); + m_child_out = 0; + } + } + +#else + + void async_subprocess::close_stdout (void) + { + if (m_child_out != -1) + { + ::close(m_child_out); + m_child_out = -1; + } + } + +#endif + +#ifdef MSWINDOWS + + void async_subprocess::close_stderr (void) + { + if (m_child_err) + { + CloseHandle(m_child_err); + m_child_err = 0; + } + } + +#else + + void async_subprocess::close_stderr (void) + { + if (m_child_err != -1) + { + ::close(m_child_err); + m_child_err = -1; + } + } + +#endif + + bool async_subprocess::error(void) const + { + return m_err != 0; + } + + int async_subprocess::error_number(void) const + { + return m_err; + } + +#ifdef MSWINDOWS + + std::string async_subprocess::error_text(void) const + { + if (!error()) return std::string(); + char* message; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS, + 0, + m_err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&message, + 0,0); + std::string result = message; + LocalFree(message); + // the error message is for some perverse reason newline terminated - remove this + if (result[result.size()-1] == '\n') + result.erase(result.end()-1); + if (result[result.size()-1] == '\r') + result.erase(result.end()-1); + return result; + } + +#else + + std::string async_subprocess::error_text(void) const + { + if (!error()) return std::string(); + char* text = strerror(m_err); + if (text) return std::string(text); + return "error number " + dformat("%d",m_err); + } + +#endif + + int async_subprocess::exit_status(void) const + { + return m_status; + } + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus diff --git a/src/stlplus/portability/subprocesses.hpp b/src/stlplus/portability/subprocesses.hpp index 5056463..c60aa5e 100644 --- a/src/stlplus/portability/subprocesses.hpp +++ b/src/stlplus/portability/subprocesses.hpp @@ -1,282 +1,288 @@ -#ifndef STLPLUS_SUBPROCESSES -#define STLPLUS_SUBPROCESSES -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Platform-independent wrapper around the very platform-specific handling of -// subprocesses. Uses the C++ convention that all resources must be contained in -// an object so that when a subprocess object goes out of scope the subprocess -// itself gets closed down. - -//////////////////////////////////////////////////////////////////////////////// -#include "portability_fixes.hpp" -#ifdef MSWINDOWS -#include -#endif -#include -#include -#include -#include // for std::pair - why is this not defined separately? - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // Argument vector class - // allows manipulation of argv-like vectors - // includes splitting of command lines into argvectors as per the shell - // (removing quotes) and the reverse conversion (adding quotes where necessary) - - class arg_vector - { - private: - char** m_argv; - - public: - // create an empty vector - arg_vector (void); - - // copy constructor (yes it copies) - arg_vector (const arg_vector&); - - // construct from an argv - arg_vector (char**); - - // construct from a command-line string - // includes de-quoting of values - arg_vector (const std::string&); - arg_vector (const char*); - - ~arg_vector (void); - - // assignment operators are compatible with the constructors - arg_vector& operator = (const arg_vector&); - arg_vector& operator = (char**); - arg_vector& operator = (const std::string&); - arg_vector& operator = (const char*); - - // add an argument to the vector - arg_vector& operator += (const std::string&); - arg_vector& operator -= (const std::string&); - - // insert/clear an argument at a certain index - // adding is like the other array classes - it moves the current item at index - // up one (and all subsequent values) to make room - void insert (unsigned index, const std::string&) throw(std::out_of_range); - void clear (unsigned index) throw(std::out_of_range); - void clear (void); - - // number of values in the vector (including argv[0], the command itself - unsigned size (void) const; - - // type conversion to the argv type - operator char** (void) const; - // function-based version of the above for people who don't like type conversions - char** argv (void) const; - - // access individual values in the vector - char* operator [] (unsigned index) const throw(std::out_of_range); - - // special-case access of the command name (e.g. to do path lookup on the command) - char* argv0 (void) const throw(std::out_of_range); - - // get the command-line string represented by this vector - // includes escaping of special characters and quoting - std::string image (void) const; - }; - - //////////////////////////////////////////////////////////////////////////////// - // Environment class - // Allows manipulation of an environment vector - // This is typically used to create an environment to be used by a subprocess - // It does NOT modify the environment of the current process - -#ifdef MSWINDOWS -#define ENVIRON_TYPE char* -#else -#define ENVIRON_TYPE char** -#endif - - class env_vector - { - private: - ENVIRON_TYPE m_env; - - public: - // create an env_vector vector from the current process - env_vector (void); - env_vector (const env_vector&); - ~env_vector (void); - - env_vector& operator = (const env_vector&); - - void clear (void); - - // manipulate the env_vector by adding or removing variables - // adding a name that already exists replaces its value - void add (const std::string& name, const std::string& value); - bool remove (const std::string& name); - - // get the value associated with a name - // the first uses an indexed notation (e.g. env["PATH"] ) - // the second is a function based form (e.g. env.get("PATH")) - std::string operator [] (const std::string& name) const; - std::string get (const std::string& name) const; - - // number of name=value pairs in the env_vector - unsigned size (void) const; - - // get the name=value pairs by index (in the range 0 to size()-1) - std::pair operator [] (unsigned index) const throw(std::out_of_range); - std::pair get (unsigned index) const throw(std::out_of_range); - - // access the env_vector as an envp type - used for passing to subprocesses - ENVIRON_TYPE envp (void) const; - }; - - //////////////////////////////////////////////////////////////////////////////// - -#ifdef MSWINDOWS -#define PID_TYPE PROCESS_INFORMATION -#define PIPE_TYPE HANDLE -#else -#define PID_TYPE int -#define PIPE_TYPE int -#endif - - //////////////////////////////////////////////////////////////////////////////// - // Synchronous subprocess - - class subprocess - { - private: - - PID_TYPE m_pid; -#ifdef MSWINDOWS - HANDLE m_job; -#endif - PIPE_TYPE m_child_in; - PIPE_TYPE m_child_out; - PIPE_TYPE m_child_err; - env_vector m_env; - int m_err; - int m_status; - - public: - subprocess(void); - virtual ~subprocess(void); - - void add_variable(const std::string& name, const std::string& value); - bool remove_variable(const std::string& name); - - bool spawn(const std::string& path, const arg_vector& argv, - bool connect_stdin = false, bool connect_stdout = false, bool connect_stderr = false); - bool spawn(const std::string& command_line, - bool connect_stdin = false, bool connect_stdout = false, bool connect_stderr = false); - - virtual bool callback(void); - bool kill(void); - - int write_stdin(std::string& buffer); - int read_stdout(std::string& buffer); - int read_stderr(std::string& buffer); - - void close_stdin(void); - void close_stdout(void); - void close_stderr(void); - - bool error(void) const; - int error_number(void) const; - std::string error_text(void) const; - - int exit_status(void) const; - - private: - // disallow copying - subprocess(const subprocess&); - subprocess& operator=(const subprocess&); - }; - - //////////////////////////////////////////////////////////////////////////////// - // Preconfigured subprocess which executes a command and captures its output - - class backtick_subprocess : public subprocess - { - private: - std::string m_text; - public: - backtick_subprocess(void); - virtual bool callback(void); - bool spawn(const std::string& path, const arg_vector& argv); - bool spawn(const std::string& command_line); - std::vector text(void) const; - }; - - std::vector backtick(const std::string& path, const arg_vector& argv); - std::vector backtick(const std::string& command_line); - - //////////////////////////////////////////////////////////////////////////////// - // Asynchronous subprocess - - class async_subprocess - { - private: - PID_TYPE m_pid; -#ifdef MSWINDOWS - HANDLE m_job; -#endif - PIPE_TYPE m_child_in; - PIPE_TYPE m_child_out; - PIPE_TYPE m_child_err; - env_vector m_env; - int m_err; - int m_status; - void set_error(int); - - public: - async_subprocess(void); - virtual ~async_subprocess(void); - - void add_variable(const std::string& name, const std::string& value); - bool remove_variable(const std::string& name); - - bool spawn(const std::string& path, const arg_vector& argv, - bool connect_stdin = false, bool connect_stdout = false, bool connect_stderr = false); - bool spawn(const std::string& command_line, - bool connect_stdin = false, bool connect_stdout = false, bool connect_stderr = false); - - virtual bool callback(void); - bool tick(void); - bool kill(void); - - int write_stdin(std::string& buffer); - int read_stdout(std::string& buffer); - int read_stderr(std::string& buffer); - - void close_stdin(void); - void close_stdout(void); - void close_stderr(void); - - bool error(void) const; - int error_number(void) const; - std::string error_text(void) const; - - int exit_status(void) const; - - private: - // disallow copying - async_subprocess(const async_subprocess&); - async_subprocess& operator=(const async_subprocess&); - }; - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#endif +#ifndef STLPLUS_SUBPROCESSES +#define STLPLUS_SUBPROCESSES +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Platform-independent wrapper around the very platform-specific handling of +// subprocesses. Uses the C++ convention that all resources must be contained in +// an object so that when a subprocess object goes out of scope the subprocess +// itself gets closed down. + +//////////////////////////////////////////////////////////////////////////////// +#include "portability_fixes.hpp" +#ifdef MSWINDOWS +#include +#endif +#include +#include +#include +#include // for std::pair - why is this not defined separately? + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // Argument vector class + // allows manipulation of argv-like vectors + // includes splitting of command lines into argvectors as per the shell + // (removing quotes) and the reverse conversion (adding quotes where necessary) + + class arg_vector + { + private: + char** m_argv; + + public: + // create an empty vector + arg_vector (void); + + // copy constructor (yes it copies) + arg_vector (const arg_vector&); + + // construct from an argv + arg_vector (char**); + + // construct from a command-line string + // includes de-quoting of values + arg_vector (const std::string&); + arg_vector (const char*); + + ~arg_vector (void); + + // assignment operators are compatible with the constructors + arg_vector& operator = (const arg_vector&); + arg_vector& operator = (char**); + arg_vector& operator = (const std::string&); + arg_vector& operator = (const char*); + + // add an argument to the vector + arg_vector& operator += (const std::string&); + arg_vector& operator -= (const std::string&); + + // insert/clear an argument at a certain index + // adding is like the other array classes - it moves the current item at index + // up one (and all subsequent values) to make room + void insert (unsigned index, const std::string&) throw(std::out_of_range); + void clear (unsigned index) throw(std::out_of_range); + void clear (void); + + // number of values in the vector (including argv[0], the command itself + unsigned size (void) const; + + // type conversion to the argv type + operator char** (void) const; + // function-based version of the above for people who don't like type conversions + char** argv (void) const; + + // access individual values in the vector + char* operator [] (unsigned index) const throw(std::out_of_range); + + // special-case access of the command name (e.g. to do path lookup on the command) + char* argv0 (void) const throw(std::out_of_range); + + // get the command-line string represented by this vector + // includes escaping of special characters and quoting + std::string image (void) const; + }; + + //////////////////////////////////////////////////////////////////////////////// + // Environment class + // Allows manipulation of an environment vector + // This is typically used to create an environment to be used by a subprocess + // It does NOT modify the environment of the current process + +#ifdef MSWINDOWS +#define ENVIRON_TYPE char* +#else +#define ENVIRON_TYPE char** +#endif + class subprocess; + class async_subprocess; + + class env_vector + { + private: + ENVIRON_TYPE m_env; + friend class subprocess; + friend class async_subprocess; + // access the env_vector as an envp type - used for passing to subprocesses + ENVIRON_TYPE envp (void) const; + + public: + // create an env_vector vector from the current process + env_vector (void); + env_vector (const env_vector&); + ~env_vector (void); + + env_vector& operator = (const env_vector&); + + // manipulate the env_vector by adding or removing variables + // adding a name that already exists replaces its value + void add (const std::string& name, const std::string& value); + bool remove (const std::string& name); + void clear (void); + + // get the value associated with a name + // the first uses an indexed notation (e.g. env["PATH"] ) + // the second is a function based form (e.g. env.get("PATH")) + bool present(const std::string& name) const; + std::string operator [] (const std::string& name) const; + std::string get (const std::string& name) const; + + // number of name=value pairs in the env_vector + unsigned size (void) const; + + // get the name=value pairs by index (in the range 0 to size()-1) + std::pair operator [] (unsigned index) const throw(std::out_of_range); + std::pair get (unsigned index) const throw(std::out_of_range); + }; + + //////////////////////////////////////////////////////////////////////////////// + +#ifdef MSWINDOWS +#define PID_TYPE PROCESS_INFORMATION +#define PIPE_TYPE HANDLE +#else +#define PID_TYPE int +#define PIPE_TYPE int +#endif + + //////////////////////////////////////////////////////////////////////////////// + // Synchronous subprocess + + class subprocess + { + protected: + + PID_TYPE m_pid; +#ifdef MSWINDOWS + HANDLE m_job; +#endif + PIPE_TYPE m_child_in; + PIPE_TYPE m_child_out; + PIPE_TYPE m_child_err; + env_vector m_env; + int m_err; + int m_status; + void set_error(int); + + public: + subprocess(void); + virtual ~subprocess(void); + + void add_variable(const std::string& name, const std::string& value); + bool remove_variable(const std::string& name); + const env_vector& get_variables(void) const; + + bool spawn(const std::string& path, const arg_vector& argv, + bool connect_stdin = false, bool connect_stdout = false, bool connect_stderr = false); + bool spawn(const std::string& command_line, + bool connect_stdin = false, bool connect_stdout = false, bool connect_stderr = false); + + virtual bool callback(void); + bool kill(void); + + int write_stdin(std::string& buffer); + int read_stdout(std::string& buffer); + int read_stderr(std::string& buffer); + + void close_stdin(void); + void close_stdout(void); + void close_stderr(void); + + bool error(void) const; + int error_number(void) const; + std::string error_text(void) const; + + int exit_status(void) const; + + private: + // disallow copying + subprocess(const subprocess&); + subprocess& operator=(const subprocess&); + }; + + //////////////////////////////////////////////////////////////////////////////// + // Preconfigured subprocess which executes a command and captures its output + + class backtick_subprocess : public subprocess + { + protected: + std::string m_text; + public: + backtick_subprocess(void); + virtual bool callback(void); + bool spawn(const std::string& path, const arg_vector& argv); + bool spawn(const std::string& command_line); + std::vector text(void) const; + }; + + std::vector backtick(const std::string& path, const arg_vector& argv); + std::vector backtick(const std::string& command_line); + + //////////////////////////////////////////////////////////////////////////////// + // Asynchronous subprocess + + class async_subprocess + { + protected: + PID_TYPE m_pid; +#ifdef MSWINDOWS + HANDLE m_job; +#endif + PIPE_TYPE m_child_in; + PIPE_TYPE m_child_out; + PIPE_TYPE m_child_err; + env_vector m_env; + int m_err; + int m_status; + void set_error(int); + + public: + async_subprocess(void); + virtual ~async_subprocess(void); + + void add_variable(const std::string& name, const std::string& value); + bool remove_variable(const std::string& name); + const env_vector& get_variables(void) const; + + bool spawn(const std::string& path, const arg_vector& argv, + bool connect_stdin = false, bool connect_stdout = false, bool connect_stderr = false); + bool spawn(const std::string& command_line, + bool connect_stdin = false, bool connect_stdout = false, bool connect_stderr = false); + + virtual bool callback(void); + bool tick(void); + bool kill(void); + + int write_stdin(std::string& buffer); + int read_stdout(std::string& buffer); + int read_stderr(std::string& buffer); + + void close_stdin(void); + void close_stdout(void); + void close_stderr(void); + + bool error(void) const; + int error_number(void) const; + std::string error_text(void) const; + + int exit_status(void) const; + + private: + // disallow copying + async_subprocess(const async_subprocess&); + async_subprocess& operator=(const async_subprocess&); + }; + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#endif diff --git a/src/stlplus/portability/tcp.hpp b/src/stlplus/portability/tcp.hpp index 023b174..ace1364 100644 --- a/src/stlplus/portability/tcp.hpp +++ b/src/stlplus/portability/tcp.hpp @@ -1,17 +1,17 @@ -#ifndef STLPLUS_TCP -#define STLPLUS_TCP -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// A deprecated legacy header - please use tcp_socket.hpp - -//////////////////////////////////////////////////////////////////////////////// - -#include "portability_fixes.hpp" -#include "tcp_sockets.hpp" - -#endif +#ifndef STLPLUS_TCP +#define STLPLUS_TCP +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// A deprecated legacy header - please use tcp_socket.hpp + +//////////////////////////////////////////////////////////////////////////////// + +#include "portability_fixes.hpp" +#include "tcp_sockets.hpp" + +#endif diff --git a/src/stlplus/portability/tcp_sockets.cpp b/src/stlplus/portability/tcp_sockets.cpp index b15f638..2b8fa63 100644 --- a/src/stlplus/portability/tcp_sockets.cpp +++ b/src/stlplus/portability/tcp_sockets.cpp @@ -1,119 +1,119 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// - -#include "tcp_sockets.hpp" - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - ////////////////////////////////////////////////////////////////////////////// - // TCP Connection - - - TCP_connection::TCP_connection(const IP_socket& socket) : IP_socket(socket) - { - } - - TCP_connection::TCP_connection(void) : IP_socket(TCP) - { - } - - unsigned short TCP_connection::port(void) const - { - return remote_port(); - } - - //////////////////////////////////////////////////////////////////////////////// - // Server - - TCP_server::TCP_server(void) : IP_socket(TCP) - { - } - - TCP_server::TCP_server(unsigned short port, unsigned short queue) : IP_socket(TCP) - { - initialise(port,queue); - } - - bool TCP_server::initialise(unsigned short port, unsigned short queue) - { - if (!IP_socket::bind_any(port)) return false; - return IP_socket::listen(queue); - } - - TCP_connection TCP_server::accept(void) - { - return TCP_connection(IP_socket::accept()); - } - - bool TCP_server::connection_ready(unsigned timeout) - { - return accept_ready(timeout); - } - - TCP_connection TCP_server::connection(void) - { - return accept(); - } - - ////////////////////////////////////////////////////////////////////////////// - // Client - - TCP_client::TCP_client(void) : IP_socket(TCP) - { - } - - TCP_client::TCP_client(const std::string& address, unsigned short port, unsigned int timeout) : IP_socket(TCP) - { - initialise(address,port,timeout); - } - - TCP_client::TCP_client(unsigned long address, unsigned short port, unsigned int timeout) : IP_socket(TCP) - { - initialise(address,port,timeout); - } - - bool TCP_client::initialise(unsigned long remote_address, unsigned short remote_port, unsigned int timeout) - { - if (!IP_socket::connect(remote_address, remote_port)) - { - close(); - return false; - } - if (timeout && !IP_socket::connected(timeout)) - { - close(); - return false; - } - return true; - } - - bool TCP_client::initialise(const std::string& address, unsigned short remote_port, unsigned int timeout) - { - // lookup the address and convert it into an IP number - unsigned long remote_address = IP_socket::ip_lookup(address); - if (!remote_address) return false; - return initialise(remote_address, remote_port, timeout); - } - - unsigned short TCP_client::port(void) const - { - return remote_port(); - } - - unsigned long TCP_client::address(void) const - { - return remote_address(); - } - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// + +#include "tcp_sockets.hpp" + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + ////////////////////////////////////////////////////////////////////////////// + // TCP Connection + + + TCP_connection::TCP_connection(const IP_socket& socket) : IP_socket(socket) + { + } + + TCP_connection::TCP_connection(void) : IP_socket(TCP) + { + } + + unsigned short TCP_connection::port(void) const + { + return remote_port(); + } + + //////////////////////////////////////////////////////////////////////////////// + // Server + + TCP_server::TCP_server(void) : IP_socket(TCP) + { + } + + TCP_server::TCP_server(unsigned short port, unsigned short queue) : IP_socket(TCP) + { + initialise(port,queue); + } + + bool TCP_server::initialise(unsigned short port, unsigned short queue) + { + if (!IP_socket::bind_any(port)) return false; + return IP_socket::listen(queue); + } + + TCP_connection TCP_server::accept(void) + { + return TCP_connection(IP_socket::accept()); + } + + bool TCP_server::connection_ready(unsigned timeout) + { + return accept_ready(timeout); + } + + TCP_connection TCP_server::connection(void) + { + return accept(); + } + + ////////////////////////////////////////////////////////////////////////////// + // Client + + TCP_client::TCP_client(void) : IP_socket(TCP) + { + } + + TCP_client::TCP_client(const std::string& address, unsigned short port, unsigned int timeout) : IP_socket(TCP) + { + initialise(address,port,timeout); + } + + TCP_client::TCP_client(unsigned long address, unsigned short port, unsigned int timeout) : IP_socket(TCP) + { + initialise(address,port,timeout); + } + + bool TCP_client::initialise(unsigned long remote_address, unsigned short remote_port, unsigned int timeout) + { + if (!IP_socket::connect(remote_address, remote_port)) + { + close(); + return false; + } + if (timeout && !IP_socket::connected(timeout)) + { + close(); + return false; + } + return true; + } + + bool TCP_client::initialise(const std::string& address, unsigned short remote_port, unsigned int timeout) + { + // lookup the address and convert it into an IP number + unsigned long remote_address = IP_socket::ip_lookup(address); + if (!remote_address) return false; + return initialise(remote_address, remote_port, timeout); + } + + unsigned short TCP_client::port(void) const + { + return remote_port(); + } + + unsigned long TCP_client::address(void) const + { + return remote_address(); + } + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus diff --git a/src/stlplus/portability/tcp_sockets.hpp b/src/stlplus/portability/tcp_sockets.hpp index df46f0b..0e3ff5b 100644 --- a/src/stlplus/portability/tcp_sockets.hpp +++ b/src/stlplus/portability/tcp_sockets.hpp @@ -1,385 +1,385 @@ -#ifndef STLPLUS_TCP_SOCKET -#define STLPLUS_TCP_SOCKET -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// A platform-independent (Unix and Windows anyway) interface to TCP sockets - -//////////////////////////////////////////////////////////////////////////////// - -#include "portability_fixes.hpp" -#include "ip_sockets.hpp" -#include - -namespace stlplus -{ - - ////////////////////////////////////////////////////////////////////////////// - // Server Classes: A server creates a listening port which waits for incoming - // connections. This is placed on the port number appropriate for the service - // - for example, a Telnet server would typically use port 23. For a new - // service you should of course use any port not allocated to a standard - // service. I believe that RFC 1700 defines the standard service port numbers. - // When an incoming connection is made, the server accepts it and in the - // process creates a new connection on a different port. This leaves the - // standard port listening for further connections. In effect, the server - // farms out the handling of the connections themselves and only takes - // responsibility for accepting the connection. This is reflected in the class - // structure. A TCP_server performs the listening and accepting roles, but - // creates a TCP_connection to handle the accepted connection. - ////////////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////////////// - // connection class created by TCP_server when a connection is accepted - // this is then used to perform any communications with the remote client - - class TCP_connection : protected IP_socket - { - private: - // constructor to actually initialise the class - can only be constructed by TCP_server - friend class TCP_server; - TCP_connection(const IP_socket& socket); - - public: - - //////////////////////////////////////////////////////////////////////////// - // constructors/destructors - - // create an uninitialised connection - TCP_connection(void); - - //////////////////////////////////////////////////////////////////////////// - // initialisation, connection control - // Note: TCP connections can only be initialised by a TCP server - - // test whether this is an initialised socket - // - returns whether this is initialised - // bool initialised(void) const; - using IP_socket::initialised; - - // close, i.e. disconnect the socket - // - returns a success flag - // bool close(void); - using IP_socket::close; - - //////////////////////////////////////////////////////////////////////////// - // sending/receiving - - // test whether a socket is connected and ready to send data, returns if ready or on timeout - // - timeout: how long to wait in microseconds if not connected yet (blocking) - // - returns status - // bool send_ready(unsigned timeout = 0); - using IP_socket::send_ready; - - // send data through the socket - if the data is long only part of it may - // be sent. The sent part is removed from the data, so the same string can - // be sent again and again until it is empty. - // - data: string containing data to be sent - any data successfully sent is removed - // - returns success flag - // bool send (std::string& data); - using IP_socket::send; - - // test whether a socket is connected and ready to receive data, returns if ready or on timeout - // - timeout: how long to wait in microseconds if not connected yet (blocking) - // - returns status - // bool receive_ready(unsigned timeout = 0); - using IP_socket::receive_ready; - - // receive data through the socket - if the data is long only part of it - // may be received. The received data is appended to the string, building - // it up in stages, so the same string can be received again and again - // until all information has been received. - // - data: string receiving data from socket - any data successfully received is appended - // - returns success flag - // bool receive (std::string& data); - using IP_socket::receive; - - //////////////////////////////////////////////////////////////////////////// - // informational - - // the local port number of the connection - // - returns the port number, 0 if not bound to a port - // unsigned short local_port(void) const; - using IP_socket::local_port; - - // the remote address of the connection - // - returns the address, 0 if not connected - // unsigned long remote_address(void) const; - using IP_socket::remote_address; - - // the remote port number of the connection - // - returns the port number, 0 if not connected to a port - // unsigned short remote_port(void) const; - using IP_socket::remote_port; - - //////////////////////////////////////////////////////////////////////////// - // error handling - // errors are set internally - // an error code of 0 is the test for no error, don't rely on the message being an empty string - // an error code != 0 means an error, then there will be a message explaining the error - - // if an error is set it stays set - so you must clear it before further operations - // void clear_error(void) const; - using IP_socket::clear_error; - - // get the error code of the last error - // int error(void) const; - using IP_socket::error; - - // get the explanatory message of the last error - // std::string message(void) const; - using IP_socket::message; - - //////////////////////////////////////////////////////////////////////////// - - // deprecated version of remote_port - unsigned short port(void) const; - - //////////////////////////////////////////////////////////////////////////// - }; - - ////////////////////////////////////////////////////////////////////////////// - // server class that does the listening on the designated port - // incoming connections can be queued up to a maximum queue length specified - // in the constructor/initialise - - class TCP_server : protected IP_socket - { - public: - - // create an uninitialised server - TCP_server(void); - - // initialise a socket and set it up to be a listening port - // - port: port to listen on - // - queue: length of backlog queue to manage - may be zero - // - returns success status - TCP_server(unsigned short port, unsigned short queue = 0); - - //////////////////////////////////////////////////////////////////////////// - // initialisation - - // initialise a socket and set it up to be a listening port - // - port: port to listen on - // - queue: length of backlog queue to manage - may be zero - // - returns success status - bool initialise(unsigned short port, unsigned short queue = 0); - - // test whether this is an initialised socket - // - returns whether this is initialised - // bool initialised(void) const; - using IP_socket::initialised; - - // close, i.e. disconnect the socket - // - returns a success flag - // bool close(void); - using IP_socket::close; - - ////////////////////////////////////////////////////////////////////////////// - // server operation - accepting a connection - - // test for a connection on the object's socket - only applicable if it has been set up as a listening port - // - timeout: how long to wait in microseconds if not connected yet - // - returns true if a connection is ready to be accepted - // bool accept_ready(unsigned timeout = 0); - using IP_socket::accept_ready; - - // accept a connection on the object's socket - only applicable if it has been set up as a listening port - // - returns the connection as a new socket - TCP_connection accept(void); - - //////////////////////////////////////////////////////////////////////////// - // error handling - // errors are set internally - // an error code of 0 is the test for no error, don't rely on the message being an empty string - // an error code != 0 means an error, then there will be a message explaining the error - - // if an error is set it stays set - so you must clear it before further operations - // void clear_error (void) const; - using IP_socket::clear_error; - - // get the error code of the last error - // int error(void) const; - using IP_socket::error; - - // get the explanatory message of the last error - // std::string message(void) const; - using IP_socket::message; - - ////////////////////////////////////////////////////////////////////////////// - - // deprecated versions of accept_ready and accept - bool connection_ready(unsigned timeout = 0); - TCP_connection connection(void); - - ////////////////////////////////////////////////////////////////////////////// - }; - - //////////////////////////////////////////////////////////////////////////////// - // Client Class: a client is simpler in that there is no listening port - you - // just create a connection and get on with it. Thus the client class does the - // whole job - create the connection and handle communications to/from it. - // - // Blocking mode: To use the client in blocking mode, use non-zero timeout for - // the initialisation method. In this mode, the connection operation must - // complete before the call will return or an error is indicated if the - // timeout is reached without completion. This usage was designed for - // applications which either just to TCP and nothing else or which do TCP - // operations in a separate thread. - // - // Non-blocking mode: To use the client in non-blocking mode, use a zero - // timeout for the initialisation method. Instead, you can ask it if it has - // connected once you've initialised it. It is not an error for it to be - // initialised but not connected. This usage was designed so that you can poll - // the connection periodically to implement a timeout for as long as you like for - // the connection to occur without blocking the thread that uses the client. - // - // In both modes, the send_ready/receive_ready methods can be called with any - // timeout including zero. - - class TCP_client : protected IP_socket - { - public: - - // create an uninitialised client - TCP_client(void); - - // client connect to a server - // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) - // - remote_port: port number of remote host - // - timeout: if 0, don't wait; if >0 wait for that microseconds for connection to be confirmed - TCP_client(const std::string& remote_address, unsigned short remote_port, unsigned timeout = 0); - - // client connect to a server - // - remote_address: IP address as a long integer - generated by stlplus::ip_lookup - // - remote_port: port number of remote host - // - timeout: if 0, don't wait; if >0 wait for that microseconds for connection to be confirmed - TCP_client(unsigned long remote_address, unsigned short remote_port, unsigned timeout = 0); - - //////////////////////////////////////////////////////////////////////////// - // initialisation, connection - - // function for performing IP lookup (i.e. gethostbyname) - // could be standalone but making it a member means that it can use the socket's error handler - // i.e. if this fails, the sockets error code will be set - clear it to use the socket again - // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) - // - returns the IP address as a long integer - zero if there's an error - // unsigned long ip_lookup(const std::string& remote_address); - using IP_socket::ip_lookup; - - // client connect to a server - // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) - // - remote_port: port number of remote host - // - timeout: if 0, don't wait; if >0 wait for that microseconds for connection to be confirmed - // - returns a success flag - bool initialise(const std::string& remote_address, unsigned short remote_port, unsigned timeout = 0); - - // client connect to a server - // - remote_address: IP address as a long integer - generated by stlplus::ip_lookup - // - remote_port: port number of remote host - // - timeout: if 0, don't wait; if >0 wait for that microseconds for connection to be confirmed - // - returns a success flag - bool initialise(unsigned long remote_address, unsigned short remote_port, unsigned timeout = 0); - - // test whether this is an initialised socket - // - returns whether this is initialised - // bool initialised(void) const; - using IP_socket::initialised; - - // test whether a socket is connected and ready to communicate, returns on successful connect or timeout - // - timeout: how long to wait in microseconds if not connected yet - // - returns success flag - // bool connected(unsigned timeout = 0); - using IP_socket::connected; - - // close, i.e. disconnect the socket - // - returns a success flag - // bool close(void); - using IP_socket::close; - - //////////////////////////////////////////////////////////////////////////// - // sending/receiving - - // test whether a socket is connected and ready to send data, returns if ready or on timeout - // - timeout: how long to wait in microseconds if not connected yet (blocking) - // - returns status - // bool send_ready(unsigned timeout = 0); - using IP_socket::send_ready; - - // send data through the socket - if the data is long only part of it may - // be sent. The sent part is removed from the data, so the same string can - // be sent again and again until it is empty. - // - data: string containing data to be sent - any data successfully sent is removed - // - returns success flag - // bool send (std::string& data); - using IP_socket::send; - - // test whether a socket is connected and ready to receive data, returns if ready or on timeout - // - timeout: how long to wait in microseconds if not connected yet (blocking) - // - returns status - // bool receive_ready(unsigned timeout = 0); - using IP_socket::receive_ready; - - // receive data through the socket - if the data is long only part of it - // may be received. The received data is appended to the string, building - // it up in stages, so the same string can be received again and again - // until all information has been received. - // - data: string receiving data from socket - any data successfully received is appended - // - returns success flag - // bool receive (std::string& data); - using IP_socket::receive; - - //////////////////////////////////////////////////////////////////////////// - // informational - - // the local port number of the connection - // - returns the port number, 0 if not bound to a port - // unsigned short local_port(void) const; - using IP_socket::local_port; - - // the remote address of the connection - // - returns the address, 0 if not connected - // unsigned long remote_address(void) const; - using IP_socket::remote_address; - - // the remote port number of the connection - // - returns the port number, 0 if not connected to a port - // unsigned short remote_port(void) const; - using IP_socket::remote_port; - - //////////////////////////////////////////////////////////////////////////// - // error handling - // errors are set internally - // an error code of 0 is the test for no error, don't rely on the message being an empty string - // an error code != 0 means an error, then there will be a message explaining the error - - // if an error is set it stays set - so you must clear it before further operations - // void clear_error (void) const; - using IP_socket::clear_error; - - // get the error code of the last error - // int error(void) const; - using IP_socket::error; - - // get the explanatory message of the last error - // std::string message(void) const; - using IP_socket::message; - - ////////////////////////////////////////////////////////////////////////////// - - // deprecated versions - unsigned long address(void) const; - unsigned short port(void) const; - - ////////////////////////////////////////////////////////////////////////////// - }; - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#endif +#ifndef STLPLUS_TCP_SOCKET +#define STLPLUS_TCP_SOCKET +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// A platform-independent (Unix and Windows anyway) interface to TCP sockets + +//////////////////////////////////////////////////////////////////////////////// + +#include "portability_fixes.hpp" +#include "ip_sockets.hpp" +#include + +namespace stlplus +{ + + ////////////////////////////////////////////////////////////////////////////// + // Server Classes: A server creates a listening port which waits for incoming + // connections. This is placed on the port number appropriate for the service + // - for example, a Telnet server would typically use port 23. For a new + // service you should of course use any port not allocated to a standard + // service. I believe that RFC 1700 defines the standard service port numbers. + // When an incoming connection is made, the server accepts it and in the + // process creates a new connection on a different port. This leaves the + // standard port listening for further connections. In effect, the server + // farms out the handling of the connections themselves and only takes + // responsibility for accepting the connection. This is reflected in the class + // structure. A TCP_server performs the listening and accepting roles, but + // creates a TCP_connection to handle the accepted connection. + ////////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////////// + // connection class created by TCP_server when a connection is accepted + // this is then used to perform any communications with the remote client + + class TCP_connection : protected IP_socket + { + private: + // constructor to actually initialise the class - can only be constructed by TCP_server + friend class TCP_server; + TCP_connection(const IP_socket& socket); + + public: + + //////////////////////////////////////////////////////////////////////////// + // constructors/destructors + + // create an uninitialised connection + TCP_connection(void); + + //////////////////////////////////////////////////////////////////////////// + // initialisation, connection control + // Note: TCP connections can only be initialised by a TCP server + + // test whether this is an initialised socket + // - returns whether this is initialised + // bool initialised(void) const; + using IP_socket::initialised; + + // close, i.e. disconnect the socket + // - returns a success flag + // bool close(void); + using IP_socket::close; + + //////////////////////////////////////////////////////////////////////////// + // sending/receiving + + // test whether a socket is connected and ready to send data, returns if ready or on timeout + // - timeout: how long to wait in microseconds if not connected yet (blocking) + // - returns status + // bool send_ready(unsigned timeout = 0); + using IP_socket::send_ready; + + // send data through the socket - if the data is long only part of it may + // be sent. The sent part is removed from the data, so the same string can + // be sent again and again until it is empty. + // - data: string containing data to be sent - any data successfully sent is removed + // - returns success flag + // bool send (std::string& data); + using IP_socket::send; + + // test whether a socket is connected and ready to receive data, returns if ready or on timeout + // - timeout: how long to wait in microseconds if not connected yet (blocking) + // - returns status + // bool receive_ready(unsigned timeout = 0); + using IP_socket::receive_ready; + + // receive data through the socket - if the data is long only part of it + // may be received. The received data is appended to the string, building + // it up in stages, so the same string can be received again and again + // until all information has been received. + // - data: string receiving data from socket - any data successfully received is appended + // - returns success flag + // bool receive (std::string& data); + using IP_socket::receive; + + //////////////////////////////////////////////////////////////////////////// + // informational + + // the local port number of the connection + // - returns the port number, 0 if not bound to a port + // unsigned short local_port(void) const; + using IP_socket::local_port; + + // the remote address of the connection + // - returns the address, 0 if not connected + // unsigned long remote_address(void) const; + using IP_socket::remote_address; + + // the remote port number of the connection + // - returns the port number, 0 if not connected to a port + // unsigned short remote_port(void) const; + using IP_socket::remote_port; + + //////////////////////////////////////////////////////////////////////////// + // error handling + // errors are set internally + // an error code of 0 is the test for no error, don't rely on the message being an empty string + // an error code != 0 means an error, then there will be a message explaining the error + + // if an error is set it stays set - so you must clear it before further operations + // void clear_error(void) const; + using IP_socket::clear_error; + + // get the error code of the last error + // int error(void) const; + using IP_socket::error; + + // get the explanatory message of the last error + // std::string message(void) const; + using IP_socket::message; + + //////////////////////////////////////////////////////////////////////////// + + // deprecated version of remote_port + unsigned short port(void) const; + + //////////////////////////////////////////////////////////////////////////// + }; + + ////////////////////////////////////////////////////////////////////////////// + // server class that does the listening on the designated port + // incoming connections can be queued up to a maximum queue length specified + // in the constructor/initialise + + class TCP_server : protected IP_socket + { + public: + + // create an uninitialised server + TCP_server(void); + + // initialise a socket and set it up to be a listening port + // - port: port to listen on + // - queue: length of backlog queue to manage - may be zero + // - returns success status + TCP_server(unsigned short port, unsigned short queue = 0); + + //////////////////////////////////////////////////////////////////////////// + // initialisation + + // initialise a socket and set it up to be a listening port + // - port: port to listen on + // - queue: length of backlog queue to manage - may be zero + // - returns success status + bool initialise(unsigned short port, unsigned short queue = 0); + + // test whether this is an initialised socket + // - returns whether this is initialised + // bool initialised(void) const; + using IP_socket::initialised; + + // close, i.e. disconnect the socket + // - returns a success flag + // bool close(void); + using IP_socket::close; + + ////////////////////////////////////////////////////////////////////////////// + // server operation - accepting a connection + + // test for a connection on the object's socket - only applicable if it has been set up as a listening port + // - timeout: how long to wait in microseconds if not connected yet + // - returns true if a connection is ready to be accepted + // bool accept_ready(unsigned timeout = 0); + using IP_socket::accept_ready; + + // accept a connection on the object's socket - only applicable if it has been set up as a listening port + // - returns the connection as a new socket + TCP_connection accept(void); + + //////////////////////////////////////////////////////////////////////////// + // error handling + // errors are set internally + // an error code of 0 is the test for no error, don't rely on the message being an empty string + // an error code != 0 means an error, then there will be a message explaining the error + + // if an error is set it stays set - so you must clear it before further operations + // void clear_error (void) const; + using IP_socket::clear_error; + + // get the error code of the last error + // int error(void) const; + using IP_socket::error; + + // get the explanatory message of the last error + // std::string message(void) const; + using IP_socket::message; + + ////////////////////////////////////////////////////////////////////////////// + + // deprecated versions of accept_ready and accept + bool connection_ready(unsigned timeout = 0); + TCP_connection connection(void); + + ////////////////////////////////////////////////////////////////////////////// + }; + + //////////////////////////////////////////////////////////////////////////////// + // Client Class: a client is simpler in that there is no listening port - you + // just create a connection and get on with it. Thus the client class does the + // whole job - create the connection and handle communications to/from it. + // + // Blocking mode: To use the client in blocking mode, use non-zero timeout for + // the initialisation method. In this mode, the connection operation must + // complete before the call will return or an error is indicated if the + // timeout is reached without completion. This usage was designed for + // applications which either just to TCP and nothing else or which do TCP + // operations in a separate thread. + // + // Non-blocking mode: To use the client in non-blocking mode, use a zero + // timeout for the initialisation method. Instead, you can ask it if it has + // connected once you've initialised it. It is not an error for it to be + // initialised but not connected. This usage was designed so that you can poll + // the connection periodically to implement a timeout for as long as you like for + // the connection to occur without blocking the thread that uses the client. + // + // In both modes, the send_ready/receive_ready methods can be called with any + // timeout including zero. + + class TCP_client : protected IP_socket + { + public: + + // create an uninitialised client + TCP_client(void); + + // client connect to a server + // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) + // - remote_port: port number of remote host + // - timeout: if 0, don't wait; if >0 wait for that microseconds for connection to be confirmed + TCP_client(const std::string& remote_address, unsigned short remote_port, unsigned timeout = 0); + + // client connect to a server + // - remote_address: IP address as a long integer - generated by stlplus::ip_lookup + // - remote_port: port number of remote host + // - timeout: if 0, don't wait; if >0 wait for that microseconds for connection to be confirmed + TCP_client(unsigned long remote_address, unsigned short remote_port, unsigned timeout = 0); + + //////////////////////////////////////////////////////////////////////////// + // initialisation, connection + + // function for performing IP lookup (i.e. gethostbyname) + // could be standalone but making it a member means that it can use the socket's error handler + // i.e. if this fails, the sockets error code will be set - clear it to use the socket again + // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) + // - returns the IP address as a long integer - zero if there's an error + // unsigned long ip_lookup(const std::string& remote_address); + using IP_socket::ip_lookup; + + // client connect to a server + // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) + // - remote_port: port number of remote host + // - timeout: if 0, don't wait; if >0 wait for that microseconds for connection to be confirmed + // - returns a success flag + bool initialise(const std::string& remote_address, unsigned short remote_port, unsigned timeout = 0); + + // client connect to a server + // - remote_address: IP address as a long integer - generated by stlplus::ip_lookup + // - remote_port: port number of remote host + // - timeout: if 0, don't wait; if >0 wait for that microseconds for connection to be confirmed + // - returns a success flag + bool initialise(unsigned long remote_address, unsigned short remote_port, unsigned timeout = 0); + + // test whether this is an initialised socket + // - returns whether this is initialised + // bool initialised(void) const; + using IP_socket::initialised; + + // test whether a socket is connected and ready to communicate, returns on successful connect or timeout + // - timeout: how long to wait in microseconds if not connected yet + // - returns success flag + // bool connected(unsigned timeout = 0); + using IP_socket::connected; + + // close, i.e. disconnect the socket + // - returns a success flag + // bool close(void); + using IP_socket::close; + + //////////////////////////////////////////////////////////////////////////// + // sending/receiving + + // test whether a socket is connected and ready to send data, returns if ready or on timeout + // - timeout: how long to wait in microseconds if not connected yet (blocking) + // - returns status + // bool send_ready(unsigned timeout = 0); + using IP_socket::send_ready; + + // send data through the socket - if the data is long only part of it may + // be sent. The sent part is removed from the data, so the same string can + // be sent again and again until it is empty. + // - data: string containing data to be sent - any data successfully sent is removed + // - returns success flag + // bool send (std::string& data); + using IP_socket::send; + + // test whether a socket is connected and ready to receive data, returns if ready or on timeout + // - timeout: how long to wait in microseconds if not connected yet (blocking) + // - returns status + // bool receive_ready(unsigned timeout = 0); + using IP_socket::receive_ready; + + // receive data through the socket - if the data is long only part of it + // may be received. The received data is appended to the string, building + // it up in stages, so the same string can be received again and again + // until all information has been received. + // - data: string receiving data from socket - any data successfully received is appended + // - returns success flag + // bool receive (std::string& data); + using IP_socket::receive; + + //////////////////////////////////////////////////////////////////////////// + // informational + + // the local port number of the connection + // - returns the port number, 0 if not bound to a port + // unsigned short local_port(void) const; + using IP_socket::local_port; + + // the remote address of the connection + // - returns the address, 0 if not connected + // unsigned long remote_address(void) const; + using IP_socket::remote_address; + + // the remote port number of the connection + // - returns the port number, 0 if not connected to a port + // unsigned short remote_port(void) const; + using IP_socket::remote_port; + + //////////////////////////////////////////////////////////////////////////// + // error handling + // errors are set internally + // an error code of 0 is the test for no error, don't rely on the message being an empty string + // an error code != 0 means an error, then there will be a message explaining the error + + // if an error is set it stays set - so you must clear it before further operations + // void clear_error (void) const; + using IP_socket::clear_error; + + // get the error code of the last error + // int error(void) const; + using IP_socket::error; + + // get the explanatory message of the last error + // std::string message(void) const; + using IP_socket::message; + + ////////////////////////////////////////////////////////////////////////////// + + // deprecated versions + unsigned long address(void) const; + unsigned short port(void) const; + + ////////////////////////////////////////////////////////////////////////////// + }; + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#endif diff --git a/src/stlplus/portability/time.cpp b/src/stlplus/portability/time.cpp index 3ceac35..7ed646d 100644 --- a/src/stlplus/portability/time.cpp +++ b/src/stlplus/portability/time.cpp @@ -1,129 +1,129 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// -#include "time.hpp" -#include "dprintf.hpp" -#include -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - - time_t time_now(void) - { - return time(0); - } - - time_t localtime_create(int year, int month, int day, int hour, int minute, int second) - { - tm tm_time; - tm_time.tm_year = year-1900; // years are represented as an offset from 1900, for reasons unknown - tm_time.tm_mon = month-1; // internal format represents month as 0-11, but it is friendlier to take an input 1-12 - tm_time.tm_mday = day; - tm_time.tm_hour = hour; - tm_time.tm_min = minute; - tm_time.tm_sec = second; - tm_time.tm_isdst = -1; // specify that the function should work out daylight savings - time_t result = mktime(&tm_time); - return result; - } - - int localtime_year(time_t t) - { - tm* tm_time = localtime(&t); - return tm_time->tm_year + 1900; - } - - int localtime_month(time_t t) - { - tm* tm_time = localtime(&t); - return tm_time->tm_mon + 1; - } - - int localtime_day(time_t t) - { - tm* tm_time = localtime(&t); - return tm_time->tm_mday; - } - - int localtime_hour(time_t t) - { - tm* tm_time = localtime(&t); - return tm_time->tm_hour; - } - - int localtime_minute(time_t t) - { - tm* tm_time = localtime(&t); - return tm_time->tm_min; - } - - int localtime_second(time_t t) - { - tm* tm_time = localtime(&t); - return tm_time->tm_sec; - } - - int localtime_weekday(time_t t) - { - tm* tm_time = localtime(&t); - return tm_time->tm_wday; - } - - int localtime_yearday(time_t t) - { - tm* tm_time = localtime(&t); - return tm_time->tm_yday; - } - - std::string localtime_string(time_t t) - { - tm* local = localtime(&t); - std::string result = local ? asctime(local) : "*time not available*"; - // ctime appends a newline for no apparent reason - clean up - while (!result.empty() && isspace(result[result.size()-1])) - result.erase(result.size()-1,1); - return result; - } - - std::string delaytime_string(time_t seconds) - { - unsigned minutes = (unsigned)seconds / 60; - seconds %= 60; - unsigned hours = minutes / 60; - minutes %= 60; - unsigned days = hours / 24; - hours %= 24; - unsigned weeks = days / 7; - days %= 7; - std::string result; - if (weeks > 0) - result += dformat("%dw ",weeks); - if (!result.empty() || days > 0) - result += dformat("%dd ", days); - if (!result.empty() || hours > 0) - result += dformat("%d:", hours); - if (!result.empty() || minutes > 0) - { - if (!result.empty()) - result += dformat("%02d:", minutes); - else - result += dformat("%d:", minutes); - } - if (!result.empty()) - result += dformat("%02d:", seconds); - else - result += dformat("%ds", seconds); - return result; - } - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// +#include "time.hpp" +#include "dprintf.hpp" +#include +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + + time_t time_now(void) + { + return time(0); + } + + time_t localtime_create(int year, int month, int day, int hour, int minute, int second) + { + tm tm_time; + tm_time.tm_year = year-1900; // years are represented as an offset from 1900, for reasons unknown + tm_time.tm_mon = month-1; // internal format represents month as 0-11, but it is friendlier to take an input 1-12 + tm_time.tm_mday = day; + tm_time.tm_hour = hour; + tm_time.tm_min = minute; + tm_time.tm_sec = second; + tm_time.tm_isdst = -1; // specify that the function should work out daylight savings + time_t result = mktime(&tm_time); + return result; + } + + int localtime_year(time_t t) + { + tm* tm_time = localtime(&t); + return tm_time->tm_year + 1900; + } + + int localtime_month(time_t t) + { + tm* tm_time = localtime(&t); + return tm_time->tm_mon + 1; + } + + int localtime_day(time_t t) + { + tm* tm_time = localtime(&t); + return tm_time->tm_mday; + } + + int localtime_hour(time_t t) + { + tm* tm_time = localtime(&t); + return tm_time->tm_hour; + } + + int localtime_minute(time_t t) + { + tm* tm_time = localtime(&t); + return tm_time->tm_min; + } + + int localtime_second(time_t t) + { + tm* tm_time = localtime(&t); + return tm_time->tm_sec; + } + + int localtime_weekday(time_t t) + { + tm* tm_time = localtime(&t); + return tm_time->tm_wday; + } + + int localtime_yearday(time_t t) + { + tm* tm_time = localtime(&t); + return tm_time->tm_yday; + } + + std::string localtime_string(time_t t) + { + tm* local = localtime(&t); + std::string result = local ? asctime(local) : "*time not available*"; + // ctime appends a newline for no apparent reason - clean up + while (!result.empty() && isspace(result[result.size()-1])) + result.erase(result.size()-1,1); + return result; + } + + std::string delaytime_string(time_t seconds) + { + unsigned minutes = (unsigned)seconds / 60; + seconds %= 60; + unsigned hours = minutes / 60; + minutes %= 60; + unsigned days = hours / 24; + hours %= 24; + unsigned weeks = days / 7; + days %= 7; + std::string result; + if (weeks > 0) + result += dformat("%dw ",weeks); + if (!result.empty() || days > 0) + result += dformat("%dd ", days); + if (!result.empty() || hours > 0) + result += dformat("%d:", hours); + if (!result.empty() || minutes > 0) + { + if (!result.empty()) + result += dformat("%02d:", minutes); + else + result += dformat("%d:", minutes); + } + if (!result.empty()) + result += dformat("%02d:", seconds); + else + result += dformat("%ds", seconds); + return result; + } + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus diff --git a/src/stlplus/portability/time.hpp b/src/stlplus/portability/time.hpp index d9907e6..8b6e1de 100644 --- a/src/stlplus/portability/time.hpp +++ b/src/stlplus/portability/time.hpp @@ -1,55 +1,55 @@ -#ifndef STLPLUS_TIME -#define STLPLUS_TIME -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Simplified access to representations of time and conversions between them. -// The motivation for this package is that the low-level system calls for -// accessing time are ugly and therefore potentially error-prone. I hope that -// this interface is much simpler and therefore easier to use and more likely -// to yield first-time right programs. - -// time is represented as the built-in integer type time_t - this is the -// standard representation of system time in computerland and represents the -// number of seconds since midnight 1 Jan 1970, believe it or not. - -// Functions are provided here for converting to and from more -// human-comprehendable forms. - -//////////////////////////////////////////////////////////////////////////////// -#include "portability_fixes.hpp" -#include -#include - -namespace stlplus -{ - - // get the integer representing the time now - time_t time_now(void); - - // get the integer representing the requested time - the local time is expressed in the local timezone - time_t localtime_create(int year, int month, int day, int hour, int minute, int second); - - // extract human-centric form of the machine representation time_t - int localtime_year(time_t); // the year e.g. 1962 - int localtime_month(time_t); // the month, numbered 1-12 e.g. August = 8 - int localtime_day(time_t); // the day of the month numbered 1-31 e.g. 29 - int localtime_hour(time_t); // the hour of day numbered 0-23 - int localtime_minute(time_t); // minute past the hour numbered 0-59 - int localtime_second(time_t); // second past the minute numbered 0-59 - int localtime_weekday(time_t); // the day of the week numbered 0-6 with 0=Sunday - int localtime_yearday(time_t); // the number of days into the year - - // convert the integer representation of time to a human-readable form - std::string localtime_string(time_t); - - // convert a time delay in seconds to human-readable form - std::string delaytime_string(time_t); - -} // end namespace stlplus - -#endif +#ifndef STLPLUS_TIME +#define STLPLUS_TIME +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Simplified access to representations of time and conversions between them. +// The motivation for this package is that the low-level system calls for +// accessing time are ugly and therefore potentially error-prone. I hope that +// this interface is much simpler and therefore easier to use and more likely +// to yield first-time right programs. + +// time is represented as the built-in integer type time_t - this is the +// standard representation of system time in computerland and represents the +// number of seconds since midnight 1 Jan 1970, believe it or not. + +// Functions are provided here for converting to and from more +// human-comprehendable forms. + +//////////////////////////////////////////////////////////////////////////////// +#include "portability_fixes.hpp" +#include +#include + +namespace stlplus +{ + + // get the integer representing the time now + time_t time_now(void); + + // get the integer representing the requested time - the local time is expressed in the local timezone + time_t localtime_create(int year, int month, int day, int hour, int minute, int second); + + // extract human-centric form of the machine representation time_t + int localtime_year(time_t); // the year e.g. 1962 + int localtime_month(time_t); // the month, numbered 1-12 e.g. August = 8 + int localtime_day(time_t); // the day of the month numbered 1-31 e.g. 29 + int localtime_hour(time_t); // the hour of day numbered 0-23 + int localtime_minute(time_t); // minute past the hour numbered 0-59 + int localtime_second(time_t); // second past the minute numbered 0-59 + int localtime_weekday(time_t); // the day of the week numbered 0-6 with 0=Sunday + int localtime_yearday(time_t); // the number of days into the year + + // convert the integer representation of time to a human-readable form + std::string localtime_string(time_t); + + // convert a time delay in seconds to human-readable form + std::string delaytime_string(time_t); + +} // end namespace stlplus + +#endif diff --git a/src/stlplus/portability/udp_sockets.cpp b/src/stlplus/portability/udp_sockets.cpp index 92f909d..658d3fe 100644 --- a/src/stlplus/portability/udp_sockets.cpp +++ b/src/stlplus/portability/udp_sockets.cpp @@ -1,167 +1,167 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Daniel Milton adapted by Andy Rushton -// Copyright: (c) Daniel Milton, Andy Rushton 2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// - -#include "udp_sockets.hpp" - -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - //////////////////////////////////////////////////////////////////////////////// - // UDP client - //////////////////////////////////////////////////////////////////////////////// - - // create an uninitialised socket - UDP_client::UDP_client(void) : IP_socket(UDP) - { - } - - // Send/Receive datagram packets to/from the given address/remote port on the local port. - // Enables default send to remote address/port - // - remote_address: IP name or number of remote host - // - remote_port: port number of remote host - // - local_port: port number to receive on - 0 to get an ephemeral port. - UDP_client::UDP_client(const std::string& remote_address, unsigned short remote_port, unsigned short local_port) : - IP_socket(UDP) - { - initialise(remote_address, remote_port, local_port); - } - - // Send/Receive datagram packets to/from the given address/remote port on the given local port - // Enables default send to remote address/port - // - remote_address: IP address of remote host - pre-looked-up using ip_lookup - // - remote_port: port number of remote host - // - local_port: port number to receive on - 0 to get an ephemeral port. - UDP_client::UDP_client(unsigned long remote_address, unsigned short remote_port, unsigned short local_port) : - IP_socket(UDP) - { - initialise(remote_address, remote_port, local_port); - } - - // Send/Receive datagram packets to/from the given address/remote port on the local port. - // Enables default send to remote address/port - // - remote_address: IP name or number of remote host - // - remote_port: port number of remote host - // - local_port: port number to receive on - 0 to get an ephemeral port. - // - returns a success flag - bool UDP_client::initialise(const std::string& address, unsigned short remote_port, unsigned short local_port) - { - // lookup the address and convert it into an IP number - unsigned long remote_address = IP_socket::ip_lookup(address); - if (!remote_address) return false; - return initialise(remote_address, remote_port, local_port); - } - - // Send/Receive datagram packets to/from the given address/remote port on the given local port - // Enables default send to remote address/port - // - remote_address: IP address of remote host - pre-looked-up using ip_lookup - // - remote_port: port number of remote host - // - local_port: port number to receive on - 0 to get an ephemeral port. - // - returns a success flag - bool UDP_client::initialise(unsigned long remote_address, unsigned short remote_port, unsigned short local_port) - { - if (!IP_socket::bind(remote_address, local_port)) return false; - return IP_socket::connect(remote_address, remote_port); - } - - // send to the remote address/port setup in initialise, from the local port also setup in initialise. - // send data through the socket as a single datagram - // - packet: string containing data to be sent - if data is successfully sent it is removed - // - returns success flag - bool UDP_client::send(std::string& packet) - { - return IP_socket::send_packet(packet); - } - - // datagram receive - // - packet: string to receive data from datagram - if data is successfully sent it is appended - // - returns success flag - i.e. packet successfully received - bool UDP_client::receive(std::string& packet) - { - return IP_socket::receive_packet(packet); - } - - //////////////////////////////////////////////////////////////////////////////// - // UDP Server - //////////////////////////////////////////////////////////////////////////////// - - // create an uninitialised socket - UDP_server::UDP_server(void) : IP_socket(UDP) - { - } - - // Initialise socket. - // Receive datagram packets from any address on provided local receiving port. - // No default send possible. - // - local_port: port number to receive on - 0 to get an ephemeral port. - UDP_server::UDP_server(unsigned short local_port) : IP_socket(UDP) - { - initialise(local_port); - } - - // Initialise socket. - // Receive datagram packets from any address on provided local receiving port. - // No default send possible. - // - local_port: port number to receive on - 0 to get an ephemeral port. - // - returns a success flag - bool UDP_server::initialise(unsigned short local_port) - { - return IP_socket::bind_any(local_port); - } - - // send to the address/port given here, from the local port setup in initialise. - // send data through the socket as a single datagram - // - packet: string containing data to be sent - if data is successfully sent it is removed - // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) - // - remote_port: port number of remote host - // - returns success flag - bool UDP_server::send(std::string& packet, const std::string& remote_address, unsigned short remote_port) - { - unsigned long ip_address = ip_lookup(remote_address); - if (ip_address == 0) return false; - return send(packet, ip_address, remote_port); - } - - // send to the address/port given here, from the local port setup in initialise. - // send data through the socket as a single datagram - // - packet: string containing data to be sent - if data is successfully sent it is removed - // - remote_address: pre-looked-up IP address of remote host - // - remote_port: port number of remote host - // - returns success flag - bool UDP_server::send(std::string& packet, unsigned long remote_address, unsigned short remote_port) - { - return IP_socket::send_packet(packet, remote_address, remote_port); - } - - // datagram receive - // - packet: string to receive data from datagram - if data is successfully sent it is appended - // - remote_address: the address of the client that sent the packet, can then be used to reply - // - remote_port: the port of the client that sent the packet, can then be used to reply - // - returns success flag - i.e. packet successfully received - bool UDP_server::receive(std::string& packet, unsigned long& remote_address, unsigned short& remote_port) - { - return IP_socket::receive_packet(packet, remote_address, remote_port); - } - - ///////////////////////////////////////////////////////////////////////////// - // fire and forget UDP client packet send function - //////////////////////////////////////////////////////////////////////////////// - - bool UDP_send(const std::string& packet, - const std::string& remote_address, unsigned short remote_port, unsigned short local_port) - { - UDP_client client(remote_address, remote_port, local_port); - if (!client.initialised()) return false; - std::string packet_copy = packet; - return client.send(packet_copy); - } - - ///////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Daniel Milton adapted by Andy Rushton +// Copyright: (c) Daniel Milton, Andy Rushton onwards +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// + +#include "udp_sockets.hpp" + +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + //////////////////////////////////////////////////////////////////////////////// + // UDP client + //////////////////////////////////////////////////////////////////////////////// + + // create an uninitialised socket + UDP_client::UDP_client(void) : IP_socket(UDP) + { + } + + // Send/Receive datagram packets to/from the given address/remote port on the local port. + // Enables default send to remote address/port + // - remote_address: IP name or number of remote host + // - remote_port: port number of remote host + // - local_port: port number to receive on - 0 to get an ephemeral port. + UDP_client::UDP_client(const std::string& remote_address, unsigned short remote_port, unsigned short local_port) : + IP_socket(UDP) + { + initialise(remote_address, remote_port, local_port); + } + + // Send/Receive datagram packets to/from the given address/remote port on the given local port + // Enables default send to remote address/port + // - remote_address: IP address of remote host - pre-looked-up using ip_lookup + // - remote_port: port number of remote host + // - local_port: port number to receive on - 0 to get an ephemeral port. + UDP_client::UDP_client(unsigned long remote_address, unsigned short remote_port, unsigned short local_port) : + IP_socket(UDP) + { + initialise(remote_address, remote_port, local_port); + } + + // Send/Receive datagram packets to/from the given address/remote port on the local port. + // Enables default send to remote address/port + // - remote_address: IP name or number of remote host + // - remote_port: port number of remote host + // - local_port: port number to receive on - 0 to get an ephemeral port. + // - returns a success flag + bool UDP_client::initialise(const std::string& address, unsigned short remote_port, unsigned short local_port) + { + // lookup the address and convert it into an IP number + unsigned long remote_address = IP_socket::ip_lookup(address); + if (!remote_address) return false; + return initialise(remote_address, remote_port, local_port); + } + + // Send/Receive datagram packets to/from the given address/remote port on the given local port + // Enables default send to remote address/port + // - remote_address: IP address of remote host - pre-looked-up using ip_lookup + // - remote_port: port number of remote host + // - local_port: port number to receive on - 0 to get an ephemeral port. + // - returns a success flag + bool UDP_client::initialise(unsigned long remote_address, unsigned short remote_port, unsigned short local_port) + { + if (!IP_socket::bind(remote_address, local_port)) return false; + return IP_socket::connect(remote_address, remote_port); + } + + // send to the remote address/port setup in initialise, from the local port also setup in initialise. + // send data through the socket as a single datagram + // - packet: string containing data to be sent - if data is successfully sent it is removed + // - returns success flag + bool UDP_client::send(std::string& packet) + { + return IP_socket::send_packet(packet); + } + + // datagram receive + // - packet: string to receive data from datagram - if data is successfully sent it is appended + // - returns success flag - i.e. packet successfully received + bool UDP_client::receive(std::string& packet) + { + return IP_socket::receive_packet(packet); + } + + //////////////////////////////////////////////////////////////////////////////// + // UDP Server + //////////////////////////////////////////////////////////////////////////////// + + // create an uninitialised socket + UDP_server::UDP_server(void) : IP_socket(UDP) + { + } + + // Initialise socket. + // Receive datagram packets from any address on provided local receiving port. + // No default send possible. + // - local_port: port number to receive on - 0 to get an ephemeral port. + UDP_server::UDP_server(unsigned short local_port) : IP_socket(UDP) + { + initialise(local_port); + } + + // Initialise socket. + // Receive datagram packets from any address on provided local receiving port. + // No default send possible. + // - local_port: port number to receive on - 0 to get an ephemeral port. + // - returns a success flag + bool UDP_server::initialise(unsigned short local_port) + { + return IP_socket::bind_any(local_port); + } + + // send to the address/port given here, from the local port setup in initialise. + // send data through the socket as a single datagram + // - packet: string containing data to be sent - if data is successfully sent it is removed + // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) + // - remote_port: port number of remote host + // - returns success flag + bool UDP_server::send(std::string& packet, const std::string& remote_address, unsigned short remote_port) + { + unsigned long ip_address = ip_lookup(remote_address); + if (ip_address == 0) return false; + return send(packet, ip_address, remote_port); + } + + // send to the address/port given here, from the local port setup in initialise. + // send data through the socket as a single datagram + // - packet: string containing data to be sent - if data is successfully sent it is removed + // - remote_address: pre-looked-up IP address of remote host + // - remote_port: port number of remote host + // - returns success flag + bool UDP_server::send(std::string& packet, unsigned long remote_address, unsigned short remote_port) + { + return IP_socket::send_packet(packet, remote_address, remote_port); + } + + // datagram receive + // - packet: string to receive data from datagram - if data is successfully sent it is appended + // - remote_address: the address of the client that sent the packet, can then be used to reply + // - remote_port: the port of the client that sent the packet, can then be used to reply + // - returns success flag - i.e. packet successfully received + bool UDP_server::receive(std::string& packet, unsigned long& remote_address, unsigned short& remote_port) + { + return IP_socket::receive_packet(packet, remote_address, remote_port); + } + + ///////////////////////////////////////////////////////////////////////////// + // fire and forget UDP client packet send function + //////////////////////////////////////////////////////////////////////////////// + + bool UDP_send(const std::string& packet, + const std::string& remote_address, unsigned short remote_port, unsigned short local_port) + { + UDP_client client(remote_address, remote_port, local_port); + if (!client.initialised()) return false; + std::string packet_copy = packet; + return client.send(packet_copy); + } + + ///////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus diff --git a/src/stlplus/portability/udp_sockets.hpp b/src/stlplus/portability/udp_sockets.hpp index b6be81a..434e096 100644 --- a/src/stlplus/portability/udp_sockets.hpp +++ b/src/stlplus/portability/udp_sockets.hpp @@ -1,268 +1,268 @@ -#ifndef STLPLUS_UDP_SOCKET -#define STLPLUS_UDP_SOCKET -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// A platform-independent (Unix and Windows anyway) interface to UDP sockets - -//////////////////////////////////////////////////////////////////////////////// - -#include "portability_fixes.hpp" -#include "ip_sockets.hpp" -#include - -namespace stlplus -{ - - ////////////////////////////////////////////////////////////////////////////// - // UDP client - creates a connectioned socket - - class UDP_client : protected IP_socket - { - public: - - // create an uninitialised socket - UDP_client(void); - - // Send/Receive datagram packets to/from the given address/remote port on the local port. - // - remote_address: IP name or number of remote host - // - remote_port: port number of remote host - // - local_port: port number to receive on - 0 to get an ephemeral port. - UDP_client(const std::string& remote_address, unsigned short remote_port, unsigned short local_port=0); - - // Send/Receive datagram packets to/from the given address/remote port on the given local port - // Enables default send to remote address/port - // - remote_address: IP address of remote host - pre-looked-up using ip_lookup - // - remote_port: port number of remote host - // - local_port: port number to receive on - 0 to get an ephemeral port. - UDP_client(unsigned long remote_address, unsigned short remote_port, unsigned short local_port=0); - - //////////////////////////////////////////////////////////////////////////// - // initialisation, connection - - // function for performing IP lookup (i.e. gethostbyname) - // could be standalone but making it a member means that it can use the socket's error handler - // i.e. if this fails, the sockets error code will be set - clear it to use the socket again - // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) - // - returns the IP address as a long integer - zero if there's an error - // unsigned long ip_lookup(const std::string& remote_address); - using IP_socket::ip_lookup; - - // Send/Receive datagram packets to/from the given address/remote port on the local port. - // Enables default send to remote address/port - // - remote_address: IP name or number of remote host - // - remote_port: port number of remote host - // - local_port: port number to receive on - 0 to get an ephemeral port. - // - returns a success flag - bool initialise(const std::string& remote_address, unsigned short remote_port, unsigned short local_port=0); - - // Send/Receive datagram packets to/from the given address/remote port on the given local port - // Enables default send to remote address/port - // - remote_address: IP address of remote host - pre-looked-up using ip_lookup - // - remote_port: port number of remote host - // - local_port: port number to receive on - 0 to get an ephemeral port. - // - returns a success flag - bool initialise(unsigned long remote_address, unsigned short remote_port, unsigned short local_port=0); - - // test whether this is an initialised socket - // - returns whether this is initialised - // bool initialised(void) const; - using IP_socket::initialised; - - // close, i.e. disconnect the socket - // - returns a success flag - // bool close(void); - using IP_socket::close; - - //////////////////////////////////////////////////////////////////////////// - // sending/receiving - - // test whether a socket is connected and ready to send data, returns if ready or on timeout - // - timeout: how long to wait in microseconds if not connected yet (blocking) - // - returns status - // bool send_ready(unsigned timeout = 0); - using IP_socket::send_ready; - - // send to the remote address/port setup in initialise, from the local port also setup in initialise. - // send data through the socket as a single datagram - // - packet: string containing data to be sent - if data is successfully sent it is removed - // - returns success flag - bool send(std::string& packet); - - // test whether a socket is connected and ready to receive data, returns if ready or on timeout - // - timeout: how long to wait in microseconds if not connected yet (blocking) - // - returns status - // bool receive_ready(unsigned timeout = 0); - using IP_socket::receive_ready; - - // datagram receive - // - packet: string to receive data from datagram - if data is successfully sent it is appended - // - returns success flag - i.e. packet successfully received - bool receive(std::string& packet); - - //////////////////////////////////////////////////////////////////////////// - // informational - - // the local port number of the connection - // returns the port number, 0 if not bound to a port - // unsigned short local_port(void) const; - using IP_socket::local_port; - - // the remote address of the connection - // returns the address, 0 if ANY address - // unsigned long remote_address(void) const; - using IP_socket::remote_address; - - // the remote port number of the connection - // returns the port number, 0 if not bound to a port - // unsigned short remote_port(void) const; - using IP_socket::remote_port; - - //////////////////////////////////////////////////////////////////////////// - // error handling - // errors are set internally - // an error code of 0 is the test for no error, don't rely on the message being an empty string - // an error code != 0 means an error, then there will be a message explaining the error - - // if an error is set it stays set - so you must clear it before further operations - // void clear_error (void); - using IP_socket::clear_error ; - - // get the error code of the last error - // int error(void) const; - using IP_socket::error; - - // get the explanatory message of the last error - // std::string message(void) const; - using IP_socket::message; - - //////////////////////////////////////////////////////////////////////////// - - private: - IP_socket m_socket; - }; - - //////////////////////////////////////////////////////////////////////////////// - // UDP server - creates a connectionless (multicast) listener socket - - class UDP_server : protected IP_socket - { - public: - - // create an uninitialised socket - UDP_server(void); - - // Initialise socket. - // Receive datagram packets from any address on provided local receiving port. - // - local_port: port number to receive on - 0 to get an ephemeral port. - UDP_server(unsigned short local_port); - - //////////////////////////////////////////////////////////////////////////// - // initialisation, connection - - // function for performing IP lookup (i.e. gethostbyname) - // could be standalone but making it a member means that it can use the socket's error handler - // i.e. if this fails, the sockets error code will be set - clear it to use the socket again - // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) - // - returns the IP address as a long integer - zero if there's an error - // unsigned long ip_lookup(const std::string& remote_address); - using IP_socket::ip_lookup; - - // Initialise socket. - // Receive datagram packets from any address on provided local receiving port. - // - local_port: port number to receive on - 0 to get an ephemeral port. - // - returns a success flag - bool initialise(unsigned short local_port); - - // test whether this is an initialised socket - // - returns whether this is initialised - // bool initialised(void) const; - using IP_socket::initialised; - - // close, i.e. disconnect the socket - // - returns a success flag - // bool close(void); - using IP_socket::close; - - //////////////////////////////////////////////////////////////////////////// - // sending/receiving - - // test whether a socket is connected and ready to send data, returns if ready or on timeout - // - timeout: how long to wait in microseconds if not connected yet (blocking) - // - returns status - // bool send_ready(unsigned timeout = 0); - using IP_socket::send_ready; - - // send to the address/port given here, from the local port setup in initialise. - // send data through the socket as a single datagram - // - packet: string containing data to be sent - if data is successfully sent it is removed - // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) - // - remote_port: port number of remote host - // - returns success flag - bool send(std::string& packet, const std::string& remote_address, unsigned short remote_port); - - // send to the address/port given here, from the local port setup in initialise. - // send data through the socket as a single datagram - // - packet: string containing data to be sent - if data is successfully sent it is removed - // - remote_address: pre-looked-up IP address of remote host - // - remote_port: port number of remote host - // - returns success flag - bool send(std::string& packet, unsigned long remote_address, unsigned short remote_port); - - // test whether a socket is connected and ready to receive data, returns if ready or on timeout - // - timeout: how long to wait in microseconds if not connected yet (blocking) - // - returns status - // bool receive_ready(unsigned timeout = 0); - using IP_socket::receive_ready; - - // datagram receive - // - packet: string to receive data from datagram - if data is successfully sent it is appended - // - remote_address: the address of the client that sent the packet, can then be used to reply - // - remote_port: the port of the client that sent the packet, can then be used to reply - // - returns success flag - i.e. packet successfully received - bool receive(std::string& packet, unsigned long& remote_address, unsigned short& remote_port); - - //////////////////////////////////////////////////////////////////////////// - // informational - - // the local port number of the connection - // returns the port number, 0 if not bound to a port - // unsigned short local_port(void) const; - using IP_socket::local_port; - - //////////////////////////////////////////////////////////////////////////// - // error handling - // errors are set internally - // an error code of 0 is the test for no error, don't rely on the message being an empty string - // an error code != 0 means an error, then there will be a message explaining the error - - // if an error is set it stays set - so you must clear it before further operations - // void clear_error(void); - using IP_socket::clear_error; - - // get the error code of the last error - // int error(void) const; - using IP_socket::error; - - // get the explanatory message of the last error - // std::string message(void) const; - using IP_socket::message; - - //////////////////////////////////////////////////////////////////////////// - }; - - ///////////////////////////////////////////////////////////////////////////// - // fire and forget UDP client packet send function - - bool UDP_send(const std::string& packet, - const std::string& remote_address, unsigned short remote_port, unsigned short local_port = 0); - - //////////////////////////////////////////////////////////////////////////////// - -} // end namespace stlplus - -#endif +#ifndef STLPLUS_UDP_SOCKET +#define STLPLUS_UDP_SOCKET +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// A platform-independent (Unix and Windows anyway) interface to UDP sockets + +//////////////////////////////////////////////////////////////////////////////// + +#include "portability_fixes.hpp" +#include "ip_sockets.hpp" +#include + +namespace stlplus +{ + + ////////////////////////////////////////////////////////////////////////////// + // UDP client - creates a connectioned socket + + class UDP_client : protected IP_socket + { + public: + + // create an uninitialised socket + UDP_client(void); + + // Send/Receive datagram packets to/from the given address/remote port on the local port. + // - remote_address: IP name or number of remote host + // - remote_port: port number of remote host + // - local_port: port number to receive on - 0 to get an ephemeral port. + UDP_client(const std::string& remote_address, unsigned short remote_port, unsigned short local_port=0); + + // Send/Receive datagram packets to/from the given address/remote port on the given local port + // Enables default send to remote address/port + // - remote_address: IP address of remote host - pre-looked-up using ip_lookup + // - remote_port: port number of remote host + // - local_port: port number to receive on - 0 to get an ephemeral port. + UDP_client(unsigned long remote_address, unsigned short remote_port, unsigned short local_port=0); + + //////////////////////////////////////////////////////////////////////////// + // initialisation, connection + + // function for performing IP lookup (i.e. gethostbyname) + // could be standalone but making it a member means that it can use the socket's error handler + // i.e. if this fails, the sockets error code will be set - clear it to use the socket again + // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) + // - returns the IP address as a long integer - zero if there's an error + // unsigned long ip_lookup(const std::string& remote_address); + using IP_socket::ip_lookup; + + // Send/Receive datagram packets to/from the given address/remote port on the local port. + // Enables default send to remote address/port + // - remote_address: IP name or number of remote host + // - remote_port: port number of remote host + // - local_port: port number to receive on - 0 to get an ephemeral port. + // - returns a success flag + bool initialise(const std::string& remote_address, unsigned short remote_port, unsigned short local_port=0); + + // Send/Receive datagram packets to/from the given address/remote port on the given local port + // Enables default send to remote address/port + // - remote_address: IP address of remote host - pre-looked-up using ip_lookup + // - remote_port: port number of remote host + // - local_port: port number to receive on - 0 to get an ephemeral port. + // - returns a success flag + bool initialise(unsigned long remote_address, unsigned short remote_port, unsigned short local_port=0); + + // test whether this is an initialised socket + // - returns whether this is initialised + // bool initialised(void) const; + using IP_socket::initialised; + + // close, i.e. disconnect the socket + // - returns a success flag + // bool close(void); + using IP_socket::close; + + //////////////////////////////////////////////////////////////////////////// + // sending/receiving + + // test whether a socket is connected and ready to send data, returns if ready or on timeout + // - timeout: how long to wait in microseconds if not connected yet (blocking) + // - returns status + // bool send_ready(unsigned timeout = 0); + using IP_socket::send_ready; + + // send to the remote address/port setup in initialise, from the local port also setup in initialise. + // send data through the socket as a single datagram + // - packet: string containing data to be sent - if data is successfully sent it is removed + // - returns success flag + bool send(std::string& packet); + + // test whether a socket is connected and ready to receive data, returns if ready or on timeout + // - timeout: how long to wait in microseconds if not connected yet (blocking) + // - returns status + // bool receive_ready(unsigned timeout = 0); + using IP_socket::receive_ready; + + // datagram receive + // - packet: string to receive data from datagram - if data is successfully sent it is appended + // - returns success flag - i.e. packet successfully received + bool receive(std::string& packet); + + //////////////////////////////////////////////////////////////////////////// + // informational + + // the local port number of the connection + // returns the port number, 0 if not bound to a port + // unsigned short local_port(void) const; + using IP_socket::local_port; + + // the remote address of the connection + // returns the address, 0 if ANY address + // unsigned long remote_address(void) const; + using IP_socket::remote_address; + + // the remote port number of the connection + // returns the port number, 0 if not bound to a port + // unsigned short remote_port(void) const; + using IP_socket::remote_port; + + //////////////////////////////////////////////////////////////////////////// + // error handling + // errors are set internally + // an error code of 0 is the test for no error, don't rely on the message being an empty string + // an error code != 0 means an error, then there will be a message explaining the error + + // if an error is set it stays set - so you must clear it before further operations + // void clear_error (void); + using IP_socket::clear_error ; + + // get the error code of the last error + // int error(void) const; + using IP_socket::error; + + // get the explanatory message of the last error + // std::string message(void) const; + using IP_socket::message; + + //////////////////////////////////////////////////////////////////////////// + + private: + IP_socket m_socket; + }; + + //////////////////////////////////////////////////////////////////////////////// + // UDP server - creates a connectionless (multicast) listener socket + + class UDP_server : protected IP_socket + { + public: + + // create an uninitialised socket + UDP_server(void); + + // Initialise socket. + // Receive datagram packets from any address on provided local receiving port. + // - local_port: port number to receive on - 0 to get an ephemeral port. + UDP_server(unsigned short local_port); + + //////////////////////////////////////////////////////////////////////////// + // initialisation, connection + + // function for performing IP lookup (i.e. gethostbyname) + // could be standalone but making it a member means that it can use the socket's error handler + // i.e. if this fails, the sockets error code will be set - clear it to use the socket again + // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) + // - returns the IP address as a long integer - zero if there's an error + // unsigned long ip_lookup(const std::string& remote_address); + using IP_socket::ip_lookup; + + // Initialise socket. + // Receive datagram packets from any address on provided local receiving port. + // - local_port: port number to receive on - 0 to get an ephemeral port. + // - returns a success flag + bool initialise(unsigned short local_port); + + // test whether this is an initialised socket + // - returns whether this is initialised + // bool initialised(void) const; + using IP_socket::initialised; + + // close, i.e. disconnect the socket + // - returns a success flag + // bool close(void); + using IP_socket::close; + + //////////////////////////////////////////////////////////////////////////// + // sending/receiving + + // test whether a socket is connected and ready to send data, returns if ready or on timeout + // - timeout: how long to wait in microseconds if not connected yet (blocking) + // - returns status + // bool send_ready(unsigned timeout = 0); + using IP_socket::send_ready; + + // send to the address/port given here, from the local port setup in initialise. + // send data through the socket as a single datagram + // - packet: string containing data to be sent - if data is successfully sent it is removed + // - remote_address: IP name (stlplus.sourceforge.net) or dotted number (216.34.181.96) + // - remote_port: port number of remote host + // - returns success flag + bool send(std::string& packet, const std::string& remote_address, unsigned short remote_port); + + // send to the address/port given here, from the local port setup in initialise. + // send data through the socket as a single datagram + // - packet: string containing data to be sent - if data is successfully sent it is removed + // - remote_address: pre-looked-up IP address of remote host + // - remote_port: port number of remote host + // - returns success flag + bool send(std::string& packet, unsigned long remote_address, unsigned short remote_port); + + // test whether a socket is connected and ready to receive data, returns if ready or on timeout + // - timeout: how long to wait in microseconds if not connected yet (blocking) + // - returns status + // bool receive_ready(unsigned timeout = 0); + using IP_socket::receive_ready; + + // datagram receive + // - packet: string to receive data from datagram - if data is successfully sent it is appended + // - remote_address: the address of the client that sent the packet, can then be used to reply + // - remote_port: the port of the client that sent the packet, can then be used to reply + // - returns success flag - i.e. packet successfully received + bool receive(std::string& packet, unsigned long& remote_address, unsigned short& remote_port); + + //////////////////////////////////////////////////////////////////////////// + // informational + + // the local port number of the connection + // returns the port number, 0 if not bound to a port + // unsigned short local_port(void) const; + using IP_socket::local_port; + + //////////////////////////////////////////////////////////////////////////// + // error handling + // errors are set internally + // an error code of 0 is the test for no error, don't rely on the message being an empty string + // an error code != 0 means an error, then there will be a message explaining the error + + // if an error is set it stays set - so you must clear it before further operations + // void clear_error(void); + using IP_socket::clear_error; + + // get the error code of the last error + // int error(void) const; + using IP_socket::error; + + // get the explanatory message of the last error + // std::string message(void) const; + using IP_socket::message; + + //////////////////////////////////////////////////////////////////////////// + }; + + ///////////////////////////////////////////////////////////////////////////// + // fire and forget UDP client packet send function + + bool UDP_send(const std::string& packet, + const std::string& remote_address, unsigned short remote_port, unsigned short local_port = 0); + + //////////////////////////////////////////////////////////////////////////////// + +} // end namespace stlplus + +#endif diff --git a/src/stlplus/portability/version.cpp b/src/stlplus/portability/version.cpp index a271b9f..b47b356 100644 --- a/src/stlplus/portability/version.cpp +++ b/src/stlplus/portability/version.cpp @@ -1,20 +1,20 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -//////////////////////////////////////////////////////////////////////////////// -#include "version.hpp" -//////////////////////////////////////////////////////////////////////////////// - -namespace stlplus -{ - - std::string version(void) - { - return STLPLUS_VERSION; - } - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +//////////////////////////////////////////////////////////////////////////////// +#include "version.hpp" +//////////////////////////////////////////////////////////////////////////////// + +namespace stlplus +{ + + std::string version(void) + { + return STLPLUS_VERSION; + } + +} // end namespace stlplus diff --git a/src/stlplus/portability/version.hpp b/src/stlplus/portability/version.hpp index 852efce..35cd938 100644 --- a/src/stlplus/portability/version.hpp +++ b/src/stlplus/portability/version.hpp @@ -1,24 +1,24 @@ -#ifndef STLPLUS_VERSION -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Contains just the STLplus version number - -//////////////////////////////////////////////////////////////////////////////// -#include "portability_fixes.hpp" -#include - -#define STLPLUS_VERSION "3.5" - -namespace stlplus -{ - - std::string version(void); - -} -//////////////////////////////////////////////////////////////////////////////// -#endif +#ifndef STLPLUS_VERSION +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Contains just the STLplus version number + +//////////////////////////////////////////////////////////////////////////////// +#include "portability_fixes.hpp" +#include + +#define STLPLUS_VERSION "3.7" + +namespace stlplus +{ + + std::string version(void); + +} +//////////////////////////////////////////////////////////////////////////////// +#endif diff --git a/src/stlplus/portability/wildcard.cpp b/src/stlplus/portability/wildcard.cpp index 2efdbca..3fbcce1 100644 --- a/src/stlplus/portability/wildcard.cpp +++ b/src/stlplus/portability/wildcard.cpp @@ -1,164 +1,164 @@ -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// Simple wildcard matching function. - -// WARNING: wheel re-invention follows -// Given that all shells perform wildcard matching, why don't the library writers put it in the C run-time???????? - -//////////////////////////////////////////////////////////////////////////////// -#include "wildcard.hpp" - -namespace stlplus -{ - - // function for testing whether a character matches a set - // I can't remember the exact rules and I have no definitive references but: - // a set contains characters, escaped characters (I think) and ranges in the form a-z - // The character '-' can only appear at the start of the set where it is not interpreted as a range - // This is a horrible mess - blame the Unix folks for making a hash of wildcards - // first expand any ranges and remove escape characters to make life more palatable - - static bool match_set (const std::string& set, char match) - { - std::string simple_set; - for (std::string::const_iterator i = set.begin(); i != set.end(); ++i) - { - switch(*i) - { - case '-': - { - if (i == set.begin()) - { - simple_set += *i; - } - else if (i+1 == set.end()) - { - return false; - } - else - { - // found a set. The first character is already in the result, so first remove it (the set might be empty) - simple_set.erase(simple_set.end()-1); - char last = *++i; - for (char ch = *(i-2); ch <= last; ch++) - { - simple_set += ch; - } - } - break; - } - case '\\': - if (i+1 == set.end()) {return false;} - simple_set += *++i; - break; - default: - simple_set += *i; - break; - } - } - std::string::size_type result = simple_set.find(match); - return result != std::string::npos; - } - - // the recursive bit - basically whenever a * is found you recursively call this for each candidate substring match - // until either it succeeds or you run out of string to match - // for each * in the wildcard another level of recursion is created - - static bool match_remainder (const std::string& wild, std::string::const_iterator wildi, const std::string& match, std::string::const_iterator matchi) - { - //cerr << "match_remainder called at " << *matchi << " with wildcard " << *wildi << endl; - while (wildi != wild.end() && matchi != match.end()) - { - //cerr << "trying to match " << *matchi << " with wildcard " << *wildi << endl; - switch(*wildi) - { - case '*': - { - ++wildi; - ++matchi; - for (std::string::const_iterator i = matchi; i != match.end(); ++i) - { - // deal with * at the end of the wildcard - there is no remainder then - if (wildi == wild.end()) - { - if (i == match.end()-1) - return true; - } - else if (match_remainder(wild, wildi, match, i)) - { - return true; - } - } - return false; - } - case '[': - { - // scan for the end of the set using a similar method for avoiding escaped characters - bool found = false; - std::string::const_iterator end = wildi + 1; - for (; !found && end != wild.end(); ++end) - { - switch(*end) - { - case ']': - { - // found the set, now match with its contents excluding the brackets - if (!match_set(wild.substr(wildi - wild.begin() + 1, end - wildi - 1), *matchi)) - return false; - found = true; - break; - } - case '\\': - if (end == wild.end()-1) - return false; - ++end; - break; - default: - break; - } - } - if (!found) - return false; - ++matchi; - wildi = end; - break; - } - case '?': - ++wildi; - ++matchi; - break; - case '\\': - if (wildi == wild.end()-1) - return false; - ++wildi; - if (*wildi != *matchi) - return false; - ++wildi; - ++matchi; - break; - default: - if (*wildi != *matchi) - return false; - ++wildi; - ++matchi; - break; - } - } - bool result = wildi == wild.end() && matchi == match.end(); - return result; - } - - // like all recursions the exported function has a simpler interface than the - // recursive function and is just a 'seed' to the recursion itself - - bool wildcard(const std::string& wild, const std::string& match) - { - return match_remainder(wild, wild.begin(), match, match.begin()); - } - -} // end namespace stlplus +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// Simple wildcard matching function. + +// WARNING: wheel re-invention follows +// Given that all shells perform wildcard matching, why don't the library writers put it in the C run-time???????? + +//////////////////////////////////////////////////////////////////////////////// +#include "wildcard.hpp" + +namespace stlplus +{ + + // function for testing whether a character matches a set + // I can't remember the exact rules and I have no definitive references but: + // a set contains characters, escaped characters (I think) and ranges in the form a-z + // The character '-' can only appear at the start of the set where it is not interpreted as a range + // This is a horrible mess - blame the Unix folks for making a hash of wildcards + // first expand any ranges and remove escape characters to make life more palatable + + static bool match_set (const std::string& set, char match) + { + std::string simple_set; + for (std::string::const_iterator i = set.begin(); i != set.end(); ++i) + { + switch(*i) + { + case '-': + { + if (i == set.begin()) + { + simple_set += *i; + } + else if (i+1 == set.end()) + { + return false; + } + else + { + // found a set. The first character is already in the result, so first remove it (the set might be empty) + simple_set.erase(simple_set.end()-1); + char last = *++i; + for (char ch = *(i-2); ch <= last; ch++) + { + simple_set += ch; + } + } + break; + } + case '\\': + if (i+1 == set.end()) {return false;} + simple_set += *++i; + break; + default: + simple_set += *i; + break; + } + } + std::string::size_type result = simple_set.find(match); + return result != std::string::npos; + } + + // the recursive bit - basically whenever a * is found you recursively call this for each candidate substring match + // until either it succeeds or you run out of string to match + // for each * in the wildcard another level of recursion is created + + static bool match_remainder (const std::string& wild, std::string::const_iterator wildi, const std::string& match, std::string::const_iterator matchi) + { + //cerr << "match_remainder called at " << *matchi << " with wildcard " << *wildi << endl; + while (wildi != wild.end() && matchi != match.end()) + { + //cerr << "trying to match " << *matchi << " with wildcard " << *wildi << endl; + switch(*wildi) + { + case '*': + { + ++wildi; + ++matchi; + for (std::string::const_iterator i = matchi; i != match.end(); ++i) + { + // deal with * at the end of the wildcard - there is no remainder then + if (wildi == wild.end()) + { + if (i == match.end()-1) + return true; + } + else if (match_remainder(wild, wildi, match, i)) + { + return true; + } + } + return false; + } + case '[': + { + // scan for the end of the set using a similar method for avoiding escaped characters + bool found = false; + std::string::const_iterator end = wildi + 1; + for (; !found && end != wild.end(); ++end) + { + switch(*end) + { + case ']': + { + // found the set, now match with its contents excluding the brackets + if (!match_set(wild.substr(wildi - wild.begin() + 1, end - wildi - 1), *matchi)) + return false; + found = true; + break; + } + case '\\': + if (end == wild.end()-1) + return false; + ++end; + break; + default: + break; + } + } + if (!found) + return false; + ++matchi; + wildi = end; + break; + } + case '?': + ++wildi; + ++matchi; + break; + case '\\': + if (wildi == wild.end()-1) + return false; + ++wildi; + if (*wildi != *matchi) + return false; + ++wildi; + ++matchi; + break; + default: + if (*wildi != *matchi) + return false; + ++wildi; + ++matchi; + break; + } + } + bool result = wildi == wild.end() && matchi == match.end(); + return result; + } + + // like all recursions the exported function has a simpler interface than the + // recursive function and is just a 'seed' to the recursion itself + + bool wildcard(const std::string& wild, const std::string& match) + { + return match_remainder(wild, wild.begin(), match, match.begin()); + } + +} // end namespace stlplus diff --git a/src/stlplus/portability/wildcard.hpp b/src/stlplus/portability/wildcard.hpp index 585a786..07adba4 100644 --- a/src/stlplus/portability/wildcard.hpp +++ b/src/stlplus/portability/wildcard.hpp @@ -1,35 +1,35 @@ -#ifndef STLPLUS_WILDCARD -#define STLPLUS_WILDCARD -//////////////////////////////////////////////////////////////////////////////// - -// Author: Andy Rushton -// Copyright: (c) Southampton University 1999-2004 -// (c) Andy Rushton 2004-2009 -// License: BSD License, see ../docs/license.html - -// This is a portable interface to wildcard matching. - -// The problem: -// * matches any number of characters - this is achieved by matching 1 and seeing if the remainder matches -// if not, try 2 characters and see if the remainder matches etc. -// this must be recursive, not iterative, so that multiple *s can appear in the same wildcard expression -// ? matches exactly one character so doesn't need the what-if approach -// \ escapes special characters such as *, ? and [ -// [] matches exactly one character in the set - the difficulty is the set can contain ranges, e.g [a-zA-Z0-9] -// a set cannot be empty and the ] character can be included by making it the first character - -//////////////////////////////////////////////////////////////////////////////// -#include "portability_fixes.hpp" -#include - -namespace stlplus -{ - - // wild = the wildcard expression - // match = the string to test against that expression - // e.g. wildcard("[a-f]*", "fred") returns true - bool wildcard(const std::string& wild, const std::string& match); - -} - -#endif +#ifndef STLPLUS_WILDCARD +#define STLPLUS_WILDCARD +//////////////////////////////////////////////////////////////////////////////// + +// Author: Andy Rushton +// Copyright: (c) Southampton University 1999-2004 +// (c) Andy Rushton 2004 onwards +// License: BSD License, see ../docs/license.html + +// This is a portable interface to wildcard matching. + +// The problem: +// * matches any number of characters - this is achieved by matching 1 and seeing if the remainder matches +// if not, try 2 characters and see if the remainder matches etc. +// this must be recursive, not iterative, so that multiple *s can appear in the same wildcard expression +// ? matches exactly one character so doesn't need the what-if approach +// \ escapes special characters such as *, ? and [ +// [] matches exactly one character in the set - the difficulty is the set can contain ranges, e.g [a-zA-Z0-9] +// a set cannot be empty and the ] character can be included by making it the first character + +//////////////////////////////////////////////////////////////////////////////// +#include "portability_fixes.hpp" +#include + +namespace stlplus +{ + + // wild = the wildcard expression + // match = the string to test against that expression + // e.g. wildcard("[a-f]*", "fred") returns true + bool wildcard(const std::string& wild, const std::string& match); + +} + +#endif -- 2.43.0