2009-07-19 08:03:54 +02:00
|
|
|
|
/*
|
2011-12-27 01:25:09 +01:00
|
|
|
|
FORMAT-UTIL.hpp - helpers for formatting and diagnostics
|
2010-12-17 23:28:49 +01:00
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
Copyright (C)
|
|
|
|
|
|
2009, Hermann Vosseler <Ichthyostega@web.de>
|
2010-12-17 23:28:49 +01:00
|
|
|
|
|
Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
* there is no entity "Lumiera.org" which holds any copyrights
* Lumiera source code is provided under the GPL Version 2+
== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''
The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!
The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
|
|
|
|
**Lumiera** is free software; you can redistribute it and/or modify it
|
|
|
|
|
|
under the terms of the GNU General Public License as published by the
|
|
|
|
|
|
Free Software Foundation; either version 2 of the License, or (at your
|
|
|
|
|
|
option) any later version. See the file COPYING for further details.
|
2010-12-17 23:28:49 +01:00
|
|
|
|
|
2009-07-19 08:03:54 +02:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-12-27 05:22:02 +01:00
|
|
|
|
/** @file format-util.hpp
|
2009-07-19 08:03:54 +02:00
|
|
|
|
** Collection of small helpers and convenience shortcuts for diagnostics & formatting.
|
2011-12-27 01:25:09 +01:00
|
|
|
|
** - util::str() performs a failsafe to-String conversion, thereby preferring a
|
|
|
|
|
|
** built-in conversion operator, falling back to just a mangled type string.
|
2016-01-08 08:20:59 +01:00
|
|
|
|
** - util::join() generates an enumerating string from elements
|
|
|
|
|
|
** of an arbitrary sequence or iterable. Elements will be passed
|
|
|
|
|
|
** through our [generic string conversion](\ref util::toString)
|
2009-07-19 08:03:54 +02:00
|
|
|
|
**
|
2011-12-27 01:25:09 +01:00
|
|
|
|
** @see FormatHelper_test
|
2018-09-21 13:46:42 +02:00
|
|
|
|
** @see [frontend for boost::format, printf-style](\ref format-string.hpp)
|
2009-07-19 08:03:54 +02:00
|
|
|
|
**
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-12-27 01:25:09 +01:00
|
|
|
|
#ifndef LIB_FORMAT_UTIL_H
|
|
|
|
|
|
#define LIB_FORMAT_UTIL_H
|
2009-07-19 08:03:54 +02:00
|
|
|
|
|
|
|
|
|
|
#include "lib/meta/trait.hpp"
|
2016-01-05 23:34:53 +01:00
|
|
|
|
#include "lib/format-obj.hpp"
|
2015-07-05 02:44:55 +02:00
|
|
|
|
#include "lib/itertools.hpp"
|
2009-09-24 23:02:40 +02:00
|
|
|
|
#include "lib/symbol.hpp"
|
2011-12-27 01:25:09 +01:00
|
|
|
|
#include "lib/util.hpp"
|
2009-07-19 08:03:54 +02:00
|
|
|
|
|
2025-02-17 18:36:23 +01:00
|
|
|
|
#include <array>
|
2009-07-19 08:03:54 +02:00
|
|
|
|
#include <string>
|
2018-04-26 12:19:45 +02:00
|
|
|
|
#include <vector>
|
2015-07-05 02:44:55 +02:00
|
|
|
|
#include <sstream>
|
2015-12-12 23:18:25 +01:00
|
|
|
|
#include <utility>
|
2009-07-19 08:03:54 +02:00
|
|
|
|
#include <typeinfo>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace util {
|
|
|
|
|
|
|
2015-07-05 02:44:55 +02:00
|
|
|
|
using lib::meta::can_IterForEach;
|
2009-07-19 08:03:54 +02:00
|
|
|
|
using std::string;
|
2017-08-11 23:52:13 +02:00
|
|
|
|
using std::forward;
|
2015-12-12 23:18:25 +01:00
|
|
|
|
using std::move;
|
2009-07-19 08:03:54 +02:00
|
|
|
|
|
|
|
|
|
|
|
2015-12-12 23:18:25 +01:00
|
|
|
|
|
|
|
|
|
|
namespace { // helper to convert arbitrary elements toString
|
|
|
|
|
|
|
|
|
|
|
|
template<class CON>
|
|
|
|
|
|
inline void
|
|
|
|
|
|
do_stringify(CON&)
|
|
|
|
|
|
{ /* do nothing */ }
|
|
|
|
|
|
|
|
|
|
|
|
template<class CON, typename X, typename...ELMS>
|
|
|
|
|
|
inline void
|
|
|
|
|
|
do_stringify(CON& container, X const& elm, ELMS const& ...args)
|
|
|
|
|
|
{
|
2016-01-08 09:14:46 +01:00
|
|
|
|
container += util::toString (elm);
|
2015-12-12 23:18:25 +01:00
|
|
|
|
do_stringify (container, args...);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template<class CON, typename...ELMS>
|
|
|
|
|
|
struct SeqContainer
|
|
|
|
|
|
: CON
|
|
|
|
|
|
{
|
|
|
|
|
|
void
|
|
|
|
|
|
operator+= (string&& s)
|
|
|
|
|
|
{
|
|
|
|
|
|
CON::push_back (move(s));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// most common case: use a vector container...
|
|
|
|
|
|
using std::vector;
|
|
|
|
|
|
|
|
|
|
|
|
template<typename X, typename...ELMS>
|
|
|
|
|
|
struct SeqContainer<vector<X>, ELMS...>
|
|
|
|
|
|
:vector<X>
|
|
|
|
|
|
{
|
|
|
|
|
|
SeqContainer()
|
|
|
|
|
|
{
|
|
|
|
|
|
this->reserve(sizeof...(ELMS));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
|
operator+= (string&& s)
|
|
|
|
|
|
{
|
|
|
|
|
|
this->emplace_back (move(s));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2016-01-04 01:38:04 +01:00
|
|
|
|
}//(end) stringify helper
|
2015-12-12 23:18:25 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** convert a sequence of elements to string
|
|
|
|
|
|
* @param elms sequence of arbitrary elements
|
2017-12-02 04:06:11 +01:00
|
|
|
|
* @tparam CON the container type to collect the results
|
2015-12-12 23:18:25 +01:00
|
|
|
|
* @return a collection of type CON, initialised by the
|
|
|
|
|
|
* string representation of the given elements
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<class CON, typename...ELMS>
|
|
|
|
|
|
inline CON
|
2023-09-29 02:44:44 +02:00
|
|
|
|
collectStr(ELMS const& ...elms)
|
2015-12-12 23:18:25 +01:00
|
|
|
|
{
|
|
|
|
|
|
SeqContainer<CON,ELMS...> storage;
|
|
|
|
|
|
do_stringify (storage, elms...);
|
|
|
|
|
|
return CON {move(storage)};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-29 02:44:44 +02:00
|
|
|
|
/** standard setup: convert to string into a vector */
|
|
|
|
|
|
template<typename...ELMS>
|
|
|
|
|
|
inline vector<string>
|
|
|
|
|
|
stringify (ELMS const& ...elms)
|
|
|
|
|
|
{
|
|
|
|
|
|
return collectStr<vector<string>> (elms...);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-04 23:30:49 +01:00
|
|
|
|
/** convert to string as transforming step in a pipeline
|
|
|
|
|
|
* @param src a "Lumiera Forward Iterator" with arbitrary result type
|
|
|
|
|
|
* @return a "Lumiera Forward Iterator" with string elements
|
|
|
|
|
|
* @see FormatHelper_test::checkStringify()
|
|
|
|
|
|
*/
|
|
|
|
|
|
template<class IT>
|
2017-08-11 23:52:13 +02:00
|
|
|
|
inline auto
|
|
|
|
|
|
stringify (IT&& src)
|
2016-02-04 23:30:49 +01:00
|
|
|
|
{
|
2025-07-05 20:08:18 +02:00
|
|
|
|
using Val = lib::meta::ValueTypeBinding<IT>::value_type;
|
2016-02-04 23:30:49 +01:00
|
|
|
|
|
2017-08-11 23:52:13 +02:00
|
|
|
|
return lib::transformIterator(forward<IT>(src), util::toString<Val>);
|
2016-02-04 23:30:49 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-12-12 23:18:25 +01:00
|
|
|
|
|
|
|
|
|
|
|
2015-07-05 02:44:55 +02:00
|
|
|
|
namespace { // helper to build range iterator on demand
|
|
|
|
|
|
template<class CON, typename TOGGLE = void>
|
|
|
|
|
|
struct _RangeIter
|
|
|
|
|
|
{
|
2025-07-05 20:08:18 +02:00
|
|
|
|
using StlIter = CON::const_iterator;
|
2015-07-05 02:44:55 +02:00
|
|
|
|
|
|
|
|
|
|
lib::RangeIter<StlIter> iter;
|
|
|
|
|
|
|
|
|
|
|
|
_RangeIter(CON const& collection)
|
|
|
|
|
|
: iter(begin(collection), end(collection))
|
|
|
|
|
|
{ }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
template<class IT>
|
2016-01-08 09:14:46 +01:00
|
|
|
|
struct _RangeIter<IT, lib::meta::enable_if< can_IterForEach<IT>> >
|
2015-07-05 02:44:55 +02:00
|
|
|
|
{
|
|
|
|
|
|
IT iter;
|
|
|
|
|
|
|
|
|
|
|
|
_RangeIter(IT&& srcIter)
|
|
|
|
|
|
: iter(std::forward<IT>(srcIter))
|
|
|
|
|
|
{ }
|
Library: investigate how a »zip iterator« can be built
Basically I am sick of writing for-loops in those cases
where the actual iteration is based on one or several data sources,
and I just need some damn index counter. Nothing against for-loops
in general — they have their valid uses — sometimes a for-loop is KISS
But in these typical cases, an iterator-based solution would be a
one-liner, when also exploiting the structured bindings of C++17
''I must admit that I want this for a loooooong time —''
...but always got intimidated again when thinking through the fine points.
Basically it „should be dead simple“ — as they say
Well — — it ''is'' simple, after getting the nasty aspects of tuple binding
and reference data types out of the way. Yesterday, while writing those
`TestFrame` test cases (which are again an example where you want to iterate
over two word sequences simultaneously and just compare them), I noticed that
last year I learned about the `std::apply`-to-fold-expression trick, and
that this solution pattern could be adapted to construct a tuple directly,
thereby circumventing most of the problems related to ''perfect forwarding''
So now we have a new util function `mapEach` (defined in `tuple-helper.hpp`)
and I have learned how to make this application completely generic.
As a second step, I implemented a proof-of-concept in `IterZip_test`,
which indeed was not really challenging, because the `IterExplorer`
is so very sophisticated by now and handles most cases with transparent
type-driven adaptors. A lot of work went into `IterExplorer` over the years,
and this pays off now.
The solution works as follows:
* apply the `lib::explore()` constructor function to the varargs
* package the resulting `IterExplorer` instantiations into a tuple
* build a »state core« implementation which just lifts out the three
iterator primitives onto this ''product type'' (i.e. the tuple)
* wrap it in yet another `IterExplorer`
* add a transformer function on top to extract a value-tuple for each ''yield'
As expected, works out-of-the-box, with all conceivable variants and wild
mixes of iterators, const, pointers, references, you name it....
PS: I changed the rendering of unsigned types in diagnostic output
to use the short notation, e.g. `uint` instead of `unsigned int`.
This dramatically improves the legibility of verification strings.
2024-11-21 23:30:07 +01:00
|
|
|
|
_RangeIter(IT const& srcIter) // note: copy here
|
2015-12-26 04:40:38 +01:00
|
|
|
|
: iter(srcIter)
|
|
|
|
|
|
{ }
|
2015-07-05 02:44:55 +02:00
|
|
|
|
|
|
|
|
|
|
};
|
2016-01-04 01:38:04 +01:00
|
|
|
|
}//(end) join helper
|
|
|
|
|
|
|
2015-07-05 02:44:55 +02:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* enumerate a collection's contents, separated by delimiter.
|
Library: investigate how a »zip iterator« can be built
Basically I am sick of writing for-loops in those cases
where the actual iteration is based on one or several data sources,
and I just need some damn index counter. Nothing against for-loops
in general — they have their valid uses — sometimes a for-loop is KISS
But in these typical cases, an iterator-based solution would be a
one-liner, when also exploiting the structured bindings of C++17
''I must admit that I want this for a loooooong time —''
...but always got intimidated again when thinking through the fine points.
Basically it „should be dead simple“ — as they say
Well — — it ''is'' simple, after getting the nasty aspects of tuple binding
and reference data types out of the way. Yesterday, while writing those
`TestFrame` test cases (which are again an example where you want to iterate
over two word sequences simultaneously and just compare them), I noticed that
last year I learned about the `std::apply`-to-fold-expression trick, and
that this solution pattern could be adapted to construct a tuple directly,
thereby circumventing most of the problems related to ''perfect forwarding''
So now we have a new util function `mapEach` (defined in `tuple-helper.hpp`)
and I have learned how to make this application completely generic.
As a second step, I implemented a proof-of-concept in `IterZip_test`,
which indeed was not really challenging, because the `IterExplorer`
is so very sophisticated by now and handles most cases with transparent
type-driven adaptors. A lot of work went into `IterExplorer` over the years,
and this pays off now.
The solution works as follows:
* apply the `lib::explore()` constructor function to the varargs
* package the resulting `IterExplorer` instantiations into a tuple
* build a »state core« implementation which just lifts out the three
iterator primitives onto this ''product type'' (i.e. the tuple)
* wrap it in yet another `IterExplorer`
* add a transformer function on top to extract a value-tuple for each ''yield'
As expected, works out-of-the-box, with all conceivable variants and wild
mixes of iterators, const, pointers, references, you name it....
PS: I changed the rendering of unsigned types in diagnostic output
to use the short notation, e.g. `uint` instead of `unsigned int`.
This dramatically improves the legibility of verification strings.
2024-11-21 23:30:07 +01:00
|
|
|
|
* @param coll something that is standard- or Lumiera-iterable
|
|
|
|
|
|
* @note Lumiera-iterator is copied when given by ref, otherwise moved,
|
|
|
|
|
|
* while in all other cases the source container is taken by const&
|
2015-07-05 02:44:55 +02:00
|
|
|
|
* @return all contents converted to string and joined into
|
|
|
|
|
|
* a single string, with separators interspersed.
|
2016-01-04 01:38:04 +01:00
|
|
|
|
* @remarks based `ostringstream`; additionally, we use our
|
|
|
|
|
|
* [failsafe string conversion](\ref util::str),
|
2015-07-05 02:44:55 +02:00
|
|
|
|
* which in turn invokes custom string conversion,
|
|
|
|
|
|
* or lexical_cast as appropriate.
|
2016-01-04 01:38:04 +01:00
|
|
|
|
* @remarks alternatively, the `boost::join` library function
|
|
|
|
|
|
* could be used, which works on _arbitrary sequences_,
|
|
|
|
|
|
* which incurs some additional weight (both in terms
|
|
|
|
|
|
* of header include and debug code size). And failures
|
|
|
|
|
|
* on template substitution tend to be hard to understand,
|
|
|
|
|
|
* since this _generic sequence_ concept is just so danm
|
|
|
|
|
|
* absolutely generic (In fact that was the reason why I
|
|
|
|
|
|
* gave up and just rolled our own `join` utility)
|
2015-07-05 02:44:55 +02:00
|
|
|
|
*/
|
2025-02-02 17:22:16 +01:00
|
|
|
|
template<class COLL>
|
2015-07-05 02:44:55 +02:00
|
|
|
|
inline string
|
2025-02-02 17:22:16 +01:00
|
|
|
|
join (COLL&& coll, string const& delim =", ")
|
2015-07-05 02:44:55 +02:00
|
|
|
|
{
|
2025-07-05 20:08:18 +02:00
|
|
|
|
using Coll = lib::meta::Strip<COLL>::TypePlain;
|
2025-02-02 17:22:16 +01:00
|
|
|
|
_RangeIter<Coll> range(std::forward<COLL>(coll)); // copies when CON is reference
|
2015-07-05 02:44:55 +02:00
|
|
|
|
|
2017-12-02 04:06:11 +01:00
|
|
|
|
auto strings = stringify (std::move (range.iter));
|
2015-07-05 02:44:55 +02:00
|
|
|
|
if (!strings) return "";
|
|
|
|
|
|
|
|
|
|
|
|
std::ostringstream buffer;
|
2017-12-17 03:15:18 +01:00
|
|
|
|
for ( ; strings; ++strings)
|
|
|
|
|
|
buffer << *strings << delim;
|
2015-07-05 02:44:55 +02:00
|
|
|
|
|
|
|
|
|
|
// chop off last delimiter
|
|
|
|
|
|
size_t len = buffer.str().length();
|
2016-03-04 23:16:34 +01:00
|
|
|
|
ASSERT (len >= delim.length());
|
2015-07-05 02:44:55 +02:00
|
|
|
|
return buffer.str().substr(0, len - delim.length());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-08-12 14:33:26 +02:00
|
|
|
|
template<class X>
|
|
|
|
|
|
inline string
|
|
|
|
|
|
join (std::initializer_list<X> const&& ili, string const& delim =", ")
|
|
|
|
|
|
{
|
|
|
|
|
|
return join (ili, delim);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-09-29 02:44:44 +02:00
|
|
|
|
// Note: offering a variant of join with var-args would create lots of ambiguities
|
2015-07-05 02:44:55 +02:00
|
|
|
|
|
2023-08-01 17:53:42 +02:00
|
|
|
|
/** shortcut: List in parentheses, separated by comma, using temporary vector */
|
|
|
|
|
|
template<typename...ARGS>
|
|
|
|
|
|
inline string
|
|
|
|
|
|
joinArgList (ARGS const& ...args)
|
|
|
|
|
|
{
|
2023-09-29 02:44:44 +02:00
|
|
|
|
return "("+join (stringify (args...))+")";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** shortcut: join directly with dashes */
|
|
|
|
|
|
template<typename...ARGS>
|
|
|
|
|
|
inline string
|
|
|
|
|
|
joinDash (ARGS const& ...args)
|
|
|
|
|
|
{
|
|
|
|
|
|
return join (stringify (args...), "-");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-04 23:56:16 +01:00
|
|
|
|
/** shortcut: join directly with dots */
|
2023-09-29 02:44:44 +02:00
|
|
|
|
template<typename...ARGS>
|
|
|
|
|
|
inline string
|
|
|
|
|
|
joinDot (ARGS const& ...args)
|
|
|
|
|
|
{
|
|
|
|
|
|
return join (stringify (args...), ".");
|
2023-08-01 17:53:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-02-02 17:22:16 +01:00
|
|
|
|
|
|
|
|
|
|
/** one-argument variant that can be forward declared... */
|
|
|
|
|
|
template<class COLL>
|
|
|
|
|
|
inline string
|
|
|
|
|
|
toStringParen (COLL&& coll)
|
|
|
|
|
|
{
|
|
|
|
|
|
return "("+join (forward<COLL> (coll))+")";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template<class COLL>
|
|
|
|
|
|
inline string
|
|
|
|
|
|
toStringBracket (COLL&& coll)
|
|
|
|
|
|
{
|
|
|
|
|
|
return "["+join (forward<COLL> (coll))+"]";
|
|
|
|
|
|
}
|
2025-02-17 18:36:23 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** convenient pretty-printer for std::array instances */
|
|
|
|
|
|
template<typename T, std::size_t N>
|
|
|
|
|
|
struct StringConv<std::array<T,N>>
|
|
|
|
|
|
{
|
|
|
|
|
|
static std::string
|
|
|
|
|
|
invoke (std::array<T,N> const& arr) noexcept
|
|
|
|
|
|
{
|
|
|
|
|
|
return util::toStringBracket (arr);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2025-02-02 17:22:16 +01:00
|
|
|
|
|
|
|
|
|
|
|
2009-07-19 08:03:54 +02:00
|
|
|
|
} // namespace util
|
2015-08-28 23:09:10 +02:00
|
|
|
|
#endif /*LIB_FORMAT_UTIL_H*/
|