455 lines
14 KiB
C++
455 lines
14 KiB
C++
// -*- C++ -*-
|
|
// Boost general library 'format' ---------------------------
|
|
// See http://www.boost.org for updates, documentation, and revision history.
|
|
|
|
// (C) Samuel Krempp 2001
|
|
// krempp@crans.ens-cachan.fr
|
|
// Permission to copy, use, modify, sell and
|
|
// distribute this software is granted provided this copyright notice appears
|
|
// in all copies. This software is provided "as is" without express or implied
|
|
// warranty, and with no claim as to its suitability for any purpose.
|
|
|
|
// ideas taken from Rudiger Loos's format class
|
|
// and Karl Nelson's ofstream (also took its parsing code as basis for printf parsing)
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// parsing.hpp : implementation of the parsing member functions
|
|
// ( parse, parse_printf_directive)
|
|
// ------------------------------------------------------------------------------
|
|
|
|
|
|
#ifndef BOOST_FORMAT_PARSING_HPP
|
|
#define BOOST_FORMAT_PARSING_HPP
|
|
|
|
|
|
#include <boost/format.hpp>
|
|
#include <boost/throw_exception.hpp>
|
|
#include <boost/assert.hpp>
|
|
|
|
|
|
namespace boost {
|
|
namespace io {
|
|
namespace detail {
|
|
|
|
template<class Stream> inline
|
|
bool wrap_isdigit(char c, Stream &os)
|
|
{
|
|
#ifndef BOOST_NO_LOCALE_ISIDIGIT
|
|
return std::isdigit(c, os.rdbuf()->getloc() );
|
|
# else
|
|
using namespace std;
|
|
return isdigit(c);
|
|
#endif
|
|
} //end- wrap_isdigit(..)
|
|
|
|
template<class Res> inline
|
|
Res str2int(const std::string& s,
|
|
std::string::size_type start,
|
|
BOOST_IO_STD ios &os,
|
|
const Res = Res(0) )
|
|
// Input : char string, with starting index
|
|
// a basic_ios& merely to call its widen/narrow member function in the desired locale.
|
|
// Effects : reads s[start:] and converts digits into an integral n, of type Res
|
|
// Returns : n
|
|
{
|
|
Res n = 0;
|
|
while(start<s.size() && wrap_isdigit(s[start], os) ) {
|
|
char cur_ch = s[start];
|
|
BOOST_ASSERT(cur_ch != 0 ); // since we called isdigit, this should not happen.
|
|
n *= 10;
|
|
n += cur_ch - '0'; // 22.2.1.1.2 of the C++ standard
|
|
++start;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
void skip_asterisk(const std::string & buf,
|
|
std::string::size_type * pos_p,
|
|
BOOST_IO_STD ios &os)
|
|
// skip printf's "asterisk-fields" directives in the format-string buf
|
|
// Input : char string, with starting index *pos_p
|
|
// a basic_ios& merely to call its widen/narrow member function in the desired locale.
|
|
// Effects : advance *pos_p by skipping printf's asterisk fields.
|
|
// Returns : nothing
|
|
{
|
|
using namespace std;
|
|
BOOST_ASSERT( pos_p != 0);
|
|
if(*pos_p >= buf.size() ) return;
|
|
if(buf[ *pos_p]=='*') {
|
|
++ (*pos_p);
|
|
while (*pos_p < buf.size() && wrap_isdigit(buf[*pos_p],os)) ++(*pos_p);
|
|
if(buf[*pos_p]=='$') ++(*pos_p);
|
|
}
|
|
}
|
|
|
|
|
|
inline void maybe_throw_exception( unsigned char exceptions)
|
|
// auxiliary func called by parse_printf_directive
|
|
// for centralising error handling
|
|
// it either throws if user sets the corresponding flag, or does nothing.
|
|
{
|
|
if(exceptions & io::bad_format_string_bit)
|
|
boost::throw_exception(io::bad_format_string());
|
|
}
|
|
|
|
|
|
|
|
bool parse_printf_directive(const std::string & buf,
|
|
std::string::size_type * pos_p,
|
|
detail::format_item * fpar,
|
|
BOOST_IO_STD ios &os,
|
|
unsigned char exceptions)
|
|
// Input : a 'printf-directive' in the format-string, starting at buf[ *pos_p ]
|
|
// a basic_ios& merely to call its widen/narrow member function in the desired locale.
|
|
// a bitset'excpetions' telling whether to throw exceptions on errors.
|
|
// Returns : true if parse somehow succeeded (possibly ignoring errors if exceptions disabled)
|
|
// false if it failed so bad that the directive should be printed verbatim
|
|
// Effects : - *pos_p is incremented so that buf[*pos_p] is the first char after the directive
|
|
// - *fpar is set with the parameters read in the directive
|
|
{
|
|
typedef format_item format_item_t;
|
|
BOOST_ASSERT( pos_p != 0);
|
|
std::string::size_type &i1 = *pos_p,
|
|
i0;
|
|
fpar->argN_ = format_item_t::argN_no_posit; // if no positional-directive
|
|
|
|
bool in_brackets=false;
|
|
if(buf[i1]=='|')
|
|
{
|
|
in_brackets=true;
|
|
if( ++i1 >= buf.size() ) {
|
|
maybe_throw_exception(exceptions);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// the flag '0' would be picked as a digit for argument order, but here it's a flag :
|
|
if(buf[i1]=='0')
|
|
goto parse_flags;
|
|
|
|
// handle argument order (%2$d) or possibly width specification: %2d
|
|
i0 = i1; // save position before digits
|
|
while (i1 < buf.size() && wrap_isdigit(buf[i1], os))
|
|
++i1;
|
|
if (i1!=i0)
|
|
{
|
|
if( i1 >= buf.size() ) {
|
|
maybe_throw_exception(exceptions);
|
|
return false;
|
|
}
|
|
int n=str2int(buf,i0, os, int(0) );
|
|
|
|
// %N% case : this is already the end of the directive
|
|
if( buf[i1] == '%' )
|
|
{
|
|
fpar->argN_ = n-1;
|
|
++i1;
|
|
if( in_brackets)
|
|
maybe_throw_exception(exceptions);
|
|
// but don't return. maybe "%" was used in lieu of '$', so we go on.
|
|
else return true;
|
|
}
|
|
|
|
if ( buf[i1]=='$' )
|
|
{
|
|
fpar->argN_ = n-1;
|
|
++i1;
|
|
}
|
|
else
|
|
{
|
|
// non-positionnal directive
|
|
fpar->ref_state_.width_ = n;
|
|
fpar->argN_ = format_item_t::argN_no_posit;
|
|
goto parse_precision;
|
|
}
|
|
}
|
|
|
|
parse_flags:
|
|
// handle flags
|
|
while ( i1 <buf.size()) // as long as char is one of + - = # 0 l h or ' '
|
|
{
|
|
// misc switches
|
|
switch (buf[i1])
|
|
{
|
|
case '\'' : break; // no effect yet. (painful to implement)
|
|
case 'l':
|
|
case 'h': // short/long modifier : for printf-comaptibility (no action needed)
|
|
break;
|
|
case '-':
|
|
fpar->ref_state_.flags_ |= std::ios::left;
|
|
break;
|
|
case '=':
|
|
fpar->pad_scheme_ |= format_item_t::centered;
|
|
break;
|
|
case ' ':
|
|
fpar->pad_scheme_ |= format_item_t::spacepad;
|
|
break;
|
|
case '+':
|
|
fpar->ref_state_.flags_ |= std::ios::showpos;
|
|
break;
|
|
case '0':
|
|
fpar->pad_scheme_ |= format_item_t::zeropad;
|
|
// need to know alignment before really setting flags,
|
|
// so just add 'zeropad' flag for now, it will be processed later.
|
|
break;
|
|
case '#':
|
|
fpar->ref_state_.flags_ |= std::ios::showpoint | std::ios::showbase;
|
|
break;
|
|
default:
|
|
goto parse_width;
|
|
}
|
|
++i1;
|
|
} // loop on flag.
|
|
if( i1>=buf.size()) {
|
|
maybe_throw_exception(exceptions);
|
|
return true;
|
|
}
|
|
|
|
parse_width:
|
|
// handle width spec
|
|
skip_asterisk(buf, &i1, os); // skips 'asterisk fields' : *, or *N$
|
|
i0 = i1; // save position before digits
|
|
while (i1<buf.size() && wrap_isdigit(buf[i1], os))
|
|
i1++;
|
|
|
|
if (i1!=i0)
|
|
{ fpar->ref_state_.width_ = str2int( buf,i0, os, std::streamsize(0) ); }
|
|
|
|
parse_precision:
|
|
if( i1>=buf.size()) {
|
|
maybe_throw_exception(exceptions);
|
|
return true;
|
|
}
|
|
// handle precision spec
|
|
if (buf[i1]=='.')
|
|
{
|
|
++i1;
|
|
skip_asterisk(buf, &i1, os);
|
|
i0 = i1; // save position before digits
|
|
while (i1<buf.size() && wrap_isdigit(buf[i1], os))
|
|
++i1;
|
|
|
|
if(i1==i0)
|
|
fpar->ref_state_.precision_ = 0;
|
|
else
|
|
fpar->ref_state_.precision_ = str2int(buf,i0, os, std::streamsize(0) );
|
|
}
|
|
|
|
// handle formatting-type flags :
|
|
while( i1<buf.size() &&
|
|
( buf[i1]=='l' || buf[i1]=='L' || buf[i1]=='h') )
|
|
++i1;
|
|
if( i1>=buf.size()) {
|
|
maybe_throw_exception(exceptions);
|
|
return true;
|
|
}
|
|
|
|
if( in_brackets && buf[i1]=='|' )
|
|
{
|
|
++i1;
|
|
return true;
|
|
}
|
|
switch (buf[i1])
|
|
{
|
|
case 'X':
|
|
fpar->ref_state_.flags_ |= std::ios::uppercase;
|
|
case 'p': // pointer => set hex.
|
|
case 'x':
|
|
fpar->ref_state_.flags_ &= ~std::ios::basefield;
|
|
fpar->ref_state_.flags_ |= std::ios::hex;
|
|
break;
|
|
|
|
case 'o':
|
|
fpar->ref_state_.flags_ &= ~std::ios::basefield;
|
|
fpar->ref_state_.flags_ |= std::ios::oct;
|
|
break;
|
|
|
|
case 'E':
|
|
fpar->ref_state_.flags_ |= std::ios::uppercase;
|
|
case 'e':
|
|
fpar->ref_state_.flags_ &= ~std::ios::floatfield;
|
|
fpar->ref_state_.flags_ |= std::ios::scientific;
|
|
|
|
fpar->ref_state_.flags_ &= ~std::ios::basefield;
|
|
fpar->ref_state_.flags_ |= std::ios::dec;
|
|
break;
|
|
|
|
case 'f':
|
|
fpar->ref_state_.flags_ &= ~std::ios::floatfield;
|
|
fpar->ref_state_.flags_ |= std::ios::fixed;
|
|
case 'u':
|
|
case 'd':
|
|
case 'i':
|
|
fpar->ref_state_.flags_ &= ~std::ios::basefield;
|
|
fpar->ref_state_.flags_ |= std::ios::dec;
|
|
break;
|
|
|
|
case 'T':
|
|
++i1;
|
|
if( i1 >= buf.size())
|
|
maybe_throw_exception(exceptions);
|
|
else
|
|
fpar->ref_state_.fill_ = buf[i1];
|
|
fpar->pad_scheme_ |= format_item_t::tabulation;
|
|
fpar->argN_ = format_item_t::argN_tabulation;
|
|
break;
|
|
case 't':
|
|
fpar->ref_state_.fill_ = ' ';
|
|
fpar->pad_scheme_ |= format_item_t::tabulation;
|
|
fpar->argN_ = format_item_t::argN_tabulation;
|
|
break;
|
|
|
|
case 'G':
|
|
fpar->ref_state_.flags_ |= std::ios::uppercase;
|
|
break;
|
|
case 'g': // 'g' conversion is default for floats.
|
|
fpar->ref_state_.flags_ &= ~std::ios::basefield;
|
|
fpar->ref_state_.flags_ |= std::ios::dec;
|
|
|
|
// CLEAR all floatield flags, so stream will CHOOSE
|
|
fpar->ref_state_.flags_ &= ~std::ios::floatfield;
|
|
break;
|
|
|
|
case 'C':
|
|
case 'c':
|
|
fpar->truncate_ = 1;
|
|
break;
|
|
case 'S':
|
|
case 's':
|
|
fpar->truncate_ = fpar->ref_state_.precision_;
|
|
fpar->ref_state_.precision_ = -1;
|
|
break;
|
|
case 'n' :
|
|
fpar->argN_ = format_item_t::argN_ignored;
|
|
break;
|
|
default:
|
|
maybe_throw_exception(exceptions);
|
|
}
|
|
++i1;
|
|
|
|
if( in_brackets )
|
|
{
|
|
if( i1<buf.size() && buf[i1]=='|' )
|
|
{
|
|
++i1;
|
|
return true;
|
|
}
|
|
else maybe_throw_exception(exceptions);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // detail namespace
|
|
} // io namespace
|
|
|
|
|
|
// -----------------------------------------------
|
|
// format :: parse(..)
|
|
|
|
void basic_format::parse(const string_t & buf)
|
|
// parse the format-string
|
|
{
|
|
using namespace std;
|
|
const char arg_mark = '%';
|
|
bool ordered_args=true;
|
|
int max_argN=-1;
|
|
string_t::size_type i1=0;
|
|
int num_items=0;
|
|
|
|
// A: find upper_bound on num_items and allocates arrays
|
|
i1=0;
|
|
while( (i1=buf.find(arg_mark,i1)) != string::npos )
|
|
{
|
|
if( i1+1 >= buf.size() ) {
|
|
if(exceptions() & io::bad_format_string_bit)
|
|
boost::throw_exception(io::bad_format_string()); // must not end in "bla bla %"
|
|
else break; // stop there, ignore last '%'
|
|
}
|
|
if(buf[i1+1] == buf[i1] ) { i1+=2; continue; } // escaped "%%" / "##"
|
|
++i1;
|
|
|
|
// in case of %N% directives, dont count it double (wastes allocations..) :
|
|
while(i1 < buf.size() && io::detail::wrap_isdigit(buf[i1],oss_)) ++i1;
|
|
if( i1 < buf.size() && buf[i1] == arg_mark ) ++ i1;
|
|
|
|
++num_items;
|
|
}
|
|
items_.assign( num_items, format_item_t() );
|
|
|
|
// B: Now the real parsing of the format string :
|
|
num_items=0;
|
|
i1 = 0;
|
|
string_t::size_type i0 = i1;
|
|
bool special_things=false;
|
|
int cur_it=0;
|
|
while( (i1=buf.find(arg_mark,i1)) != string::npos )
|
|
{
|
|
string_t & piece = (cur_it==0) ? prefix_ : items_[cur_it-1].appendix_;
|
|
|
|
if( buf[i1+1] == buf[i1] ) // escaped mark, '%%'
|
|
{
|
|
piece += buf.substr(i0, i1-i0) + buf[i1];
|
|
i1+=2; i0=i1;
|
|
continue;
|
|
}
|
|
BOOST_ASSERT( static_cast<unsigned int>(cur_it) < items_.size() || cur_it==0);
|
|
|
|
if(i1!=i0) piece += buf.substr(i0, i1-i0);
|
|
++i1;
|
|
|
|
bool parse_ok;
|
|
parse_ok = io::detail::parse_printf_directive(buf, &i1, &items_[cur_it], oss_, exceptions());
|
|
if( ! parse_ok ) continue; // the directive will be printed verbatim
|
|
|
|
i0=i1;
|
|
items_[cur_it].compute_states(); // process complex options, like zeropad, into stream params.
|
|
|
|
int argN=items_[cur_it].argN_;
|
|
if(argN == format_item_t::argN_ignored)
|
|
continue;
|
|
if(argN ==format_item_t::argN_no_posit)
|
|
ordered_args=false;
|
|
else if(argN == format_item_t::argN_tabulation) special_things=true;
|
|
else if(argN > max_argN) max_argN = argN;
|
|
++num_items;
|
|
++cur_it;
|
|
} // loop on %'s
|
|
BOOST_ASSERT(cur_it == num_items);
|
|
|
|
// store the final piece of string
|
|
string_t & piece = (cur_it==0) ? prefix_ : items_[cur_it-1].appendix_;
|
|
piece += buf.substr(i0);
|
|
|
|
if( !ordered_args)
|
|
{
|
|
if(max_argN >= 0 ) // dont mix positional with non-positionnal directives
|
|
{
|
|
if(exceptions() & io::bad_format_string_bit)
|
|
boost::throw_exception(io::bad_format_string());
|
|
// else do nothing. => positionnal arguments are processed as non-positionnal
|
|
}
|
|
// set things like it would have been with positional directives :
|
|
int non_ordered_items = 0;
|
|
for(int i=0; i< num_items; ++i)
|
|
if(items_[i].argN_ == format_item_t::argN_no_posit)
|
|
{
|
|
items_[i].argN_ = non_ordered_items;
|
|
++non_ordered_items;
|
|
}
|
|
max_argN = non_ordered_items-1;
|
|
}
|
|
|
|
// C: set some member data :
|
|
items_.resize(num_items);
|
|
|
|
if(special_things) style_ |= special_needs;
|
|
num_args_ = max_argN + 1;
|
|
if(ordered_args) style_ |= ordered;
|
|
else style_ &= ~ordered;
|
|
}
|
|
|
|
} // namespace boost
|
|
|
|
|
|
#endif // BOOST_FORMAT_PARSING_HPP
|