// -*- 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