LCOV - code coverage report
Current view: top level - tools - Tools.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 373 414 90.1 %
Date: 2026-11-05 10:19:04 Functions: 68 70 97.1 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2011-2023 The plumed team
       3             :    (see the PEOPLE file at the root of the distribution for a list of names)
       4             : 
       5             :    See http://www.plumed.org for more information.
       6             : 
       7             :    This file is part of plumed, version 2.
       8             : 
       9             :    plumed is free software: you can redistribute it and/or modify
      10             :    it under the terms of the GNU Lesser General Public License as published by
      11             :    the Free Software Foundation, either version 3 of the License, or
      12             :    (at your option) any later version.
      13             : 
      14             :    plumed is distributed in the hope that it will be useful,
      15             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      16             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17             :    GNU Lesser General Public License for more details.
      18             : 
      19             :    You should have received a copy of the GNU Lesser General Public License
      20             :    along with plumed.  If not, see <http://www.gnu.org/licenses/>.
      21             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      22             : #include "Tools.h"
      23             : #include "AtomNumber.h"
      24             : #include "Exception.h"
      25             : #include "IFile.h"
      26             : #include "lepton/Lepton.h"
      27             : #include <cctype>
      28             : #include <charconv>
      29             : #include <cstring>
      30             : #include <filesystem>
      31             : #include <limits>
      32             : #include <type_traits>
      33             : #include <iomanip>
      34             : #include <sstream>
      35             : #include <string_view>
      36             : 
      37             : namespace PLMD {
      38             : 
      39             : namespace {
      40             : /// Removes leading and trailing blanks without allocating
      41    26635161 : std::string_view trimBlanks(const std::string & str) {
      42             :   std::size_t b=0;
      43             :   std::size_t e=str.size();
      44    39617209 :   while(b<e && std::isspace(static_cast<unsigned char>(str[b]))) {
      45    12982048 :     ++b;
      46             :   }
      47    26635180 :   while(e>b && std::isspace(static_cast<unsigned char>(str[e-1]))) {
      48             :     --e;
      49             :   }
      50    26635161 :   return std::string_view(str).substr(b,e-b);
      51             : }
      52             : 
      53             : /// Fast conversion of the whole (trimmed) string with std::from_chars.
      54             : /// Returns false if std::from_chars is not available for T, or if it fails,
      55             : /// in which case the caller falls back to the slower conversions.
      56             : template<class T>
      57    26610440 : bool convertFromChars(std::string_view s,T & t) {
      58             : #if defined(__cpp_lib_to_chars)
      59             :   // floating point std::from_chars is only available when __cpp_lib_to_chars is defined
      60             :   constexpr bool available=std::is_arithmetic_v<T>;
      61             : #else
      62             :   constexpr bool available=std::is_integral_v<T>;
      63             : #endif
      64             :   if constexpr (available && !std::is_same_v<T,bool>) {
      65    26610440 :     if(s.empty()) {
      66             :       return false;
      67             :     }
      68             :     T v;
      69    26610439 :     const auto [ptr,ec]=std::from_chars(s.data(),s.data()+s.size(),v);
      70    26610439 :     if(ec!=std::errc() || ptr!=s.data()+s.size()) {
      71             :       return false;
      72             :     }
      73    24550162 :     t=v;
      74    24550162 :     return true;
      75             :   } else {
      76             :     (void) s;
      77             :     (void) t;
      78             :     return false;
      79             :   }
      80             : }
      81             : }
      82             : 
      83             : template<class T>
      84    26610445 : bool Tools::convertToAny(const std::string & str,T & t) {
      85    26610445 :   const auto trimmed=trimBlanks(str);
      86             :   if constexpr (std::is_unsigned_v<T>) {
      87             :     // istream accepts a minus sign for unsigned types and wraps the number around
      88             :     // (e.g. "-1" becomes 4294967295): negative numbers are rejected here
      89     7643437 :     if(!trimmed.empty() && trimmed[0]=='-') {
      90             :       return false;
      91             :     }
      92             :   }
      93             :   // fast path: std::from_chars is about 20 times faster than an istringstream
      94    26610440 :   if(convertFromChars(trimmed,t)) {
      95             :     return true;
      96             :   }
      97     2111689 :   std::istringstream istr(str.c_str());
      98     2060278 :   bool ok=static_cast<bool>(istr>>t);
      99     2060278 :   if(!ok) {
     100             :     return false;
     101             :   }
     102             :   std::string remaining;
     103     2008988 :   istr>>remaining;
     104     2008988 :   return remaining.length()==0;
     105     2060278 : }
     106             : 
     107     2584535 : bool Tools::convertNoexcept(const std::string & str,int & t) {
     108     2584535 :   return convertToInt(str,t);
     109             : }
     110             : 
     111           3 : bool Tools::convertNoexcept(const std::string & str,long int & t) {
     112           3 :   return convertToInt(str,t);
     113             : }
     114             : 
     115         309 : bool Tools::convertNoexcept(const std::string & str,long long int & t) {
     116         309 :   return convertToInt(str,t);
     117             : }
     118             : 
     119     5989904 : bool Tools::convertNoexcept(const std::string & str,unsigned & t) {
     120     5989904 :   return convertToInt(str,t);
     121             : }
     122             : 
     123       15529 : bool Tools::convertNoexcept(const std::string & str,long unsigned & t) {
     124       15529 :   return convertToInt(str,t);
     125             : }
     126             : 
     127          87 : bool Tools::convertNoexcept(const std::string & str,long long unsigned & t) {
     128          87 :   return convertToInt(str,t);
     129             : }
     130             : 
     131     1637917 : bool Tools::convertNoexcept(const std::string & str,AtomNumber &a) {
     132             :   // Note: AtomNumber's are NOT converted as int, so as to
     133             :   // avoid using lepton conversions.
     134             :   unsigned i;
     135     1637917 :   bool r=convertToAny(str,i);
     136     1637917 :   if(r) {
     137     1612153 :     a.setSerial(i);
     138             :   }
     139     1637917 :   return r;
     140             : }
     141             : 
     142             : template<class T>
     143     8590367 : bool Tools::convertToInt(const std::string & str,T & t) {
     144             :   // First try standard conversion
     145     8590367 :   if(convertToAny(str,t)) {
     146             :     return true;
     147             :   }
     148             :   // Then use lepton
     149             :   try {
     150     2008043 :     double r=lepton::Parser::parse(str).evaluate(lepton::Constants());
     151             : 
     152             :     // now sanity checks on the resulting number
     153             : 
     154             :     // it should not overflow the requested int type:
     155             :     // (see https://stackoverflow.com/a/526092)
     156     2008042 :     if(r>std::nextafter(std::numeric_limits<T>::max(), 0)) {
     157             :       return false;
     158             :     }
     159     2008040 :     if(r<std::nextafter(std::numeric_limits<T>::min(), 0)) {
     160             :       return false;
     161             :     }
     162             : 
     163             :     // do the actual conversion
     164     2008036 :     auto tmp=static_cast<T>(std::round(r));
     165             : 
     166             :     // it should be *very close* to itself if converted back to double
     167     2008036 :     double diff= r-static_cast<double>(tmp);
     168     2008036 :     if(diff*diff > 1e-20) {
     169             :       return false;
     170             :     }
     171             :     // this is to accomodate small numerical errors and allow e.g. exp(log(7)) to be integer
     172             : 
     173             :     // it should be change if incremented or decremented by one (see https://stackoverflow.com/a/43656140)
     174     2008031 :     if(r == static_cast<double>(tmp-1)) {
     175             :       return false;
     176             :     }
     177     2008030 :     if(r == static_cast<double>(tmp+1)) {
     178             :       return false;
     179             :     }
     180             : 
     181             :     // everything is fine, then store in t
     182     2008030 :     t=tmp;
     183     2008030 :     return true;
     184           1 :   } catch(const PLMD::lepton::Exception& exc) {
     185             :   }
     186           1 :   return false;
     187             : }
     188             : 
     189             : 
     190             : template<class T>
     191    16376760 : bool Tools::convertToReal(const std::string & str,T & t) {
     192    16376760 :   if(convertToAny(str,t)) {
     193             :     return true;
     194             :   }
     195             :   {
     196             :     // nan and inf, as written by printf (OFile writes NaNs as "nan"),
     197             :     // case insensitive and with an optional sign
     198       24716 :     auto s=trimBlanks(str);
     199             :     bool negative=false;
     200       24716 :     if(!s.empty() && (s[0]=='+' || s[0]=='-')) {
     201       12545 :       negative=(s[0]=='-');
     202             :       s.remove_prefix(1);
     203             :     }
     204       24716 :     if(caseInSensStringCompare(s,"nan")) {
     205           0 :       t=std::numeric_limits<T>::quiet_NaN();
     206           0 :       return true;
     207             :     }
     208       24716 :     if(caseInSensStringCompare(s,"inf") || caseInSensStringCompare(s,"infinity")) {
     209           1 :       t=negative ? -std::numeric_limits<T>::infinity() : std::numeric_limits<T>::infinity();
     210           1 :       return true;
     211             :     }
     212             :   }
     213       24715 :   if(str=="PI" || str=="+PI" || str=="+pi" || str=="pi") {
     214       12341 :     t=pi;
     215       12341 :     return true;
     216       12374 :   } else if(str=="-PI" || str=="-pi") {
     217       12293 :     t=-pi;
     218       12293 :     return true;
     219             :   }
     220             :   try {
     221          81 :     t=lepton::Parser::parse(str).evaluate(lepton::Constants());
     222          27 :     return true;
     223          54 :   } catch(const PLMD::lepton::Exception& exc) {
     224             :   }
     225          54 :   if( str.find("PI")!=std::string::npos ) {
     226           0 :     std::size_t pi_start=str.find_first_of("PI");
     227           0 :     if(str.substr(pi_start)!="PI") {
     228             :       return false;
     229             :     }
     230           0 :     std::istringstream nstr(str.substr(0,pi_start));
     231           0 :     T ff=0.0;
     232           0 :     bool ok=static_cast<bool>(nstr>>ff);
     233           0 :     if(!ok) {
     234             :       return false;
     235             :     }
     236           0 :     t=ff*pi;
     237             :     std::string remains;
     238           0 :     nstr>>remains;
     239           0 :     return remains.length()==0;
     240          54 :   } else if( str.find("pi")!=std::string::npos ) {
     241          14 :     std::size_t pi_start=str.find_first_of("pi");
     242          14 :     if(str.substr(pi_start)!="pi") {
     243             :       return false;
     244             :     }
     245          14 :     std::istringstream nstr(str.substr(0,pi_start));
     246          14 :     T ff=0.0;
     247          14 :     bool ok=static_cast<bool>(nstr>>ff);
     248          14 :     if(!ok) {
     249             :       return false;
     250             :     }
     251          14 :     t=ff*pi;
     252             :     std::string remains;
     253          14 :     nstr>>remains;
     254          14 :     return remains.length()==0;
     255          54 :   } else if(str=="NAN") {
     256           0 :     t=std::numeric_limits<double>::quiet_NaN();
     257           0 :     return true;
     258             :   }
     259             :   return false;
     260             : }
     261             : 
     262           4 : bool Tools::convertNoexcept(const std::string & str,float & t) {
     263           4 :   return convertToReal(str,t);
     264             : }
     265             : 
     266    16375542 : bool Tools::convertNoexcept(const std::string & str,double & t) {
     267    16375542 :   return convertToReal(str,t);
     268             : }
     269             : 
     270        1214 : bool Tools::convertNoexcept(const std::string & str,long double & t) {
     271        1214 :   return convertToReal(str,t);
     272             : }
     273             : 
     274      873055 : bool Tools::convertNoexcept(const std::string & str,std::string & t) {
     275             :   t=str;
     276      873055 :   return true;
     277             : }
     278             : 
     279     3475271 : std::vector<std::string> Tools::getWords(std::string_view line,
     280             :     const char* separators,
     281             :     int * parlevel,
     282             :     const char* parenthesis,
     283             :     bool delete_parenthesis) {
     284     3475271 :   plumed_massert(std::strlen(parenthesis)==1,"multiple parenthesis type not available");
     285     3475271 :   plumed_massert(parenthesis[0]=='(' || parenthesis[0]=='[' || parenthesis[0]=='{',
     286             :                  "only ( [ { allowed as parenthesis");
     287     3475271 :   if(!separators) {
     288             :     separators=" \t\n";
     289             :   }
     290     3475271 :   const std::string sep(separators);
     291     3475271 :   char openpar=parenthesis[0];
     292             :   char closepar;
     293             :   if(openpar=='(') {
     294             :     closepar=')';
     295             :   }
     296     3475271 :   if(openpar=='[') {
     297             :     closepar=']';
     298             :   }
     299     3475271 :   if(openpar=='{') {
     300             :     closepar='}';
     301             :   }
     302     3475271 :   std::vector<std::string> words;
     303             :   std::string word;
     304             :   int parenthesisLevel=0;
     305     3475271 :   if(parlevel) {
     306       39991 :     parenthesisLevel=*parlevel;
     307             :   }
     308   308750245 :   for(unsigned i=0; i<line.length(); i++) {
     309             :     bool found=false;
     310             :     bool onParenthesis=false;
     311   305274974 :     if( (line[i]==openpar || line[i]==closepar) && delete_parenthesis ) {
     312             :       onParenthesis=true;
     313             :     }
     314   305274974 :     if(line[i]==closepar) {
     315        3763 :       parenthesisLevel--;
     316        3763 :       plumed_assert(parenthesisLevel>=0) << "Extra closed parenthesis in '" << line << "'";
     317             :     }
     318   305274974 :     if(parenthesisLevel==0)
     319  1220660719 :       for(unsigned j=0; j<sep.length(); j++)
     320   915498646 :         if(line[i]==sep[j]) {
     321             :           found=true;
     322             :         }
     323             : // If at parenthesis level zero (outer)
     324   305162073 :     if(!(parenthesisLevel==0 && (found||onParenthesis))) {
     325   239257989 :       word.push_back(line[i]);
     326             :     }
     327   305274974 :     if(line[i]==openpar) {
     328        3763 :       parenthesisLevel++;
     329             :     }
     330   305274974 :     if(found && word.length()>0) {
     331    14827952 :       if(!parlevel) {
     332    14759684 :         plumed_assert(parenthesisLevel==0) << "Unmatching parenthesis in '" << line << "'";
     333             :       }
     334    14827952 :       words.push_back(word);
     335             :       word.clear();
     336             :     }
     337             :   }
     338     3475271 :   if(word.length()>0) {
     339     3350329 :     if(!parlevel) {
     340     3310644 :       plumed_assert(parenthesisLevel==0) << "Unmatching parenthesis in '" << line << "'";
     341             :     }
     342     3350329 :     words.push_back(word);
     343             :   }
     344     3475271 :   if(parlevel) {
     345       39991 :     *parlevel=parenthesisLevel;
     346             :   }
     347     3475271 :   return words;
     348           0 : }
     349             : 
     350     2213548 : void Tools::getWordsSimple(gch::small_vector<std::string_view> & words,std::string_view line) {
     351             :   words.clear();
     352     2213548 :   auto ptr=line.data();
     353     2213548 :   std::size_t size=0;
     354    22792990 :   for(unsigned i=0; i<line.length(); i++) {
     355    20579442 :     const bool is_separator=(line[i]==' ');
     356    20579442 :     if(!is_separator) {
     357    20500638 :       size++;
     358       78804 :     } else if(size==0) {
     359           0 :       ptr++;
     360             :     } else {
     361             :       words.emplace_back(ptr,size);
     362       78804 :       ptr=&line[i]+1;
     363       78804 :       size=0;
     364             :     }
     365             :   }
     366     2213548 :   if(size>0) {
     367             :     words.emplace_back(ptr,size);
     368             :   }
     369     2213548 : }
     370             : 
     371      520081 : void Tools::getWordsSimple(gch::small_vector<std::string_view> & words,
     372             :                            std::string_view line,
     373             :                            const std::string_view separators) {
     374             :   words.clear();
     375             :   //NOTE::this function is written assuming that we have only a level of parenthesis
     376             :   // so "{a b x},{s}" is ok, "x,{{a,r}}" is not
     377             :   // unlike getWords it will not delete the parenthesis, in the spirit of returning a view
     378             :   // to remove the parenthesis you have to use a erase-remove idiom (see parseVector)
     379             :   constexpr char openPar='{';
     380             :   constexpr char closePar = '}';
     381             :   size_t init=0;
     382             :   std::size_t size=0;
     383             :   int parlevel=0;
     384    33057229 :   for(unsigned i=0; i<line.length(); i++) {
     385             :     //being between parenthesis "suspends" the separator check
     386    32537148 :     const bool is_separator=(separators.find(line[i])!=separators.npos)&&parlevel==0;
     387    32537148 :     const bool is_par = line[i]==openPar || line[i] == closePar;
     388    32537148 :     if(!is_separator && !is_par) {
     389    24707086 :       size++;
     390     7830062 :     } else if(line[i]==openPar) {
     391          51 :       ++parlevel;
     392          51 :       ++size;
     393     7830011 :     } else if(line[i] == closePar) {
     394          51 :       --parlevel;
     395          51 :       ++size;
     396             :     } else {
     397     7829960 :       if(size==0) {
     398          21 :         init++;
     399             :       } else {
     400     7829939 :         words.emplace_back(line.substr(init,size));
     401     7829939 :         init=i+1;
     402             :         size=0;
     403             :       }
     404             :     }
     405             :   }
     406      520081 :   if(size>0) {
     407     1040136 :     words.emplace_back(line.substr(init,size));
     408             :   }
     409             : 
     410      520081 :   plumed_assert(parlevel==0) << "Unmatching parenthesis in '" << line << "'";
     411      520081 : }
     412             : 
     413       22505 : bool Tools::getParsedLine(IFile& ifile,std::vector<std::string> & words, bool trimcomments) {
     414       22505 :   std::string line("");
     415       22505 :   words.clear();
     416             :   bool stat;
     417             :   bool inside=false;
     418       22505 :   int parlevel=0;
     419             :   bool mergenext=false;
     420       49420 :   while((stat=ifile.getline(line))) {
     421       48038 :     if(trimcomments) {
     422       48016 :       trimComments(line);
     423             :     }
     424       48038 :     trim(line);
     425       48038 :     if(line.length()==0) {
     426        8047 :       continue;
     427             :     }
     428       39991 :     std::vector<std::string> w=getWords(line,NULL,&parlevel,"{",trimcomments);
     429       39991 :     if(!w.empty()) {
     430       39685 :       if(inside && *(w.begin())=="...") {
     431             :         inside=false;
     432        1588 :         if(w.size()==2) {
     433        1343 :           plumed_massert(w[1]==words[0],"second word in terminating \"...\" "+w[1]+" line, if present, should be equal to first word of directive: "+words[0]);
     434             :         }
     435        1588 :         plumed_massert(w.size()<=2,"terminating \"...\" lines cannot consist of more than two words");
     436        1588 :         w.clear();
     437        1588 :         if(!trimcomments) {
     438           4 :           words.push_back("...");
     439             :         }
     440       38097 :       } else if(*(w.end()-1)=="...") {
     441             :         inside=true;
     442             :         w.erase(w.end()-1);
     443             :       };
     444             :       int i0=0;
     445       39685 :       if(mergenext && words.size()>0 && w.size()>0) {
     446         158 :         words[words.size()-1]+=" "+w[0];
     447             :         i0=1;
     448             :       }
     449      143040 :       for(unsigned i=i0; i<w.size(); ++i) {
     450      103355 :         words.push_back(w[i]);
     451             :       }
     452             :     }
     453       39991 :     mergenext=(parlevel>0);
     454       39991 :     if(!inside) {
     455             :       break;
     456             :     }
     457       18868 :     if(!trimcomments && parlevel==0) {
     458           8 :       words.push_back("@newline");
     459             :     } else if(!trimcomments) {
     460             :       words[words.size()-1] += " @newline";
     461             :     }
     462       39991 :   }
     463       22505 :   plumed_massert(parlevel==0,"non matching parenthesis");
     464       22505 :   if(words.size()>0) {
     465       20949 :     return true;
     466             :   }
     467             :   return stat;
     468             : }
     469             : 
     470             : 
     471     3854307 : bool Tools::getline(FILE* fp,std::string & line) {
     472             :   line="";
     473             :   const int bufferlength=1024;
     474             :   char buffer[bufferlength];
     475             :   bool ret;
     476  3950664675 :   for(int i=0; i<bufferlength; i++) {
     477  3946810368 :     buffer[i]='\0';
     478             :   }
     479     3854311 :   while((ret=fgets(buffer,bufferlength,fp))) {
     480     3852892 :     line.append(buffer);
     481     3852892 :     unsigned ss=std::strlen(buffer);
     482     3852892 :     if(ss>0)
     483     3852892 :       if(buffer[ss-1]=='\n') {
     484             :         break;
     485             :       }
     486             :   };
     487     3854307 :   if(line.length()>0)
     488     3852892 :     if(*(line.end()-1)=='\n') {
     489     3852888 :       line.erase(line.end()-1);
     490             :     }
     491     3854307 :   if(line.length()>0)
     492     3852872 :     if(*(line.end()-1)=='\r') {
     493        1180 :       line.erase(line.end()-1);
     494             :     }
     495             :   // a last line without a newline is still a line
     496     3854307 :   return ret || line.length()>0;
     497             : }
     498             : 
     499     1674661 : void Tools::trim(std::string & s) {
     500     1674661 :   auto n=s.find_last_not_of(" \t");
     501     1674661 :   if(n!=std::string::npos) {
     502     1659519 :     s.resize(n+1);
     503             :   }
     504     1674661 : }
     505             : 
     506        1359 : void Tools::ltrim(std::string & s) {
     507        1359 :   auto n=s.find_first_not_of(" \t");
     508        1359 :   if(n!=std::string::npos) {
     509        2718 :     s = s.substr(n, s.length()-n);
     510             :     s.shrink_to_fit();
     511             :   }
     512        1359 : }
     513             : 
     514     1692822 : void Tools::trimComments(std::string & s) {
     515     1692822 :   auto n=s.find_first_of("#");
     516     1692822 :   if(n!=std::string::npos) {
     517             :     s.resize(n);
     518             :   }
     519     1692822 : }
     520             : 
     521      104837 : bool Tools::caseInSensStringCompare(std::string_view str1, std::string_view str2) {
     522             :   return ((str1.size() == str2.size())
     523      104837 :   && std::equal(str1.begin(), str1.end(), str2.begin(), [](char c1, char c2) {
     524       73474 :     return (std::toupper(c1) == std::toupper(c2));
     525      104837 :   }));
     526             : }
     527             : 
     528       17244 : bool Tools::getKey(std::vector<std::string>& line,
     529             :                    const std::string & key,
     530             :                    std::string & s,
     531             :                    int rep) {
     532             :   s.clear();
     533       38712 :   for(auto pl=line.begin(); pl!=line.end(); ++pl) {
     534       30564 :     std::string_view p(*pl);
     535       30564 :     if(p.length()==0) {
     536           0 :       continue;
     537             :     }
     538       30564 :     auto x=p.substr(0,key.length());
     539       30564 :     if(caseInSensStringCompare(x,key)) {
     540        9096 :       if(p.length()==key.length()) {
     541             :         return false;
     542             :       }
     543        9096 :       auto tmp=p.substr(key.length(),p.length());
     544        9096 :       constexpr std::string_view multi("@replicas:");
     545        9096 :       if(rep>=0 && startWith(tmp,multi)) {
     546             :         //can be made faster with getWordSimple? the only string allocation will be the one in s=words[rep]
     547           0 :         auto words=getWords(tmp.substr(multi.length(),tmp.length()),"\t\n ,");
     548           0 :         plumed_massert(rep<static_cast<int>(words.size()),
     549             :                        "Number of fields in " + s + " not consistent with number of replicas");
     550           0 :         s=words[rep];
     551           0 :       } else {
     552             :         s=tmp;
     553             :       }
     554             :       line.erase(pl);
     555        9096 :       return true;
     556             :     }
     557             :   };
     558             :   return false;
     559             : }
     560             : 
     561             : 
     562          27 : void getWords_replicas(gch::small_vector<std::string_view> & words,
     563             :                        std::string_view line,
     564             :                        const std::string_view separators) {
     565             :   words.clear();
     566             :   //NOTE: this function is tested in basic/rt-make-getwords
     567             :   // via unravelReplicas and parseVector
     568             : 
     569             :   //NOTE: this function ignores the first parenthesis because
     570             :   // expects something like "@replica:{a,b c}" or "@replica:a b c"
     571             :   // the user should cut the "@replica:" before running this.
     572             :   // in case of a '{' being present the vector will be returned as soon the '}' is found
     573             :   constexpr char openPar='{';
     574             :   constexpr char closePar = '}';
     575             :   size_t init=0;
     576             :   std::size_t size=0;
     577             :   int parlevel=0;
     578         240 :   for(unsigned i=0; i<line.length(); i++) {
     579             :     //being between parenthesis  do not "suspends" the separator check
     580         237 :     const bool is_separator=(separators.find(line[i])!=separators.npos)
     581         237 :                             && parlevel<=1;
     582         237 :     const bool is_par = line[i]==openPar || line[i] == closePar;
     583         237 :     if(!is_separator && !is_par) {
     584         117 :       size++;
     585         120 :     } else if(line[i]==openPar) {
     586          33 :       ++parlevel;
     587          33 :       if(parlevel == 1) {
     588          24 :         init = i+1;
     589             :       } else {
     590           9 :         ++size;
     591             :       }
     592          87 :     } else if(line[i] == closePar) {
     593          33 :       --parlevel;
     594          33 :       ++size;
     595          33 :       if(parlevel == 0) {
     596             :         --size;
     597             :         break;
     598             :       }
     599             :     } else {
     600          54 :       if(size==0) {
     601           0 :         init++;
     602             :       } else {
     603          54 :         words.emplace_back(line.substr(init,size));
     604          54 :         init=i+1;
     605             :         size=0;
     606             :       }
     607             :     }
     608             :   }
     609          27 :   if(size>0) {
     610          54 :     words.emplace_back(line.substr(init,size));
     611             :   }
     612             : 
     613          27 :   plumed_assert(parlevel==0) << "Unmatched parenthesis in '" << line << "'";
     614          27 : }
     615             : 
     616     8349960 : std::string_view Tools::unravelReplicas(std::string_view argument,
     617             :                                         int rep) {
     618             : //NOTE: Tested in /basic/rt-make-getwords
     619             : 
     620             : //NOTE: as now (exactly like before this PR) a vector will not accept only the first element as replicas:
     621             : // - @replicas:{1,2,3},4,5 do not work (KEY={@replicas:{1,2,3},4,5})
     622             : // - @replicas:{1,4,5},{2,4,5},{3,4,5} works (KEY=@replicas:{{1,4,5},{2,4,5},{3,4,5}}), it should be equivalent to the line above
     623             : // - 1,@replicas:{2,3,4},5 works (KEY={1,@replicas{2,3,4},5})
     624     8349960 :   constexpr std::string_view multi("@replicas:");
     625     8349960 :   if(rep>=0 && startWith(argument,multi)) {
     626             :     gch::small_vector<std::string_view> replicaValues;
     627          27 :     getWords_replicas(replicaValues,argument.substr(multi.size()),"\t\n ,");
     628          27 :     plumed_massert(rep<static_cast<int>(replicaValues.size()),
     629             :                    "Number of fields in " + std::string(argument) + " not consistent with number of replicas");
     630          27 :     return replicaValues[rep];
     631             :   } else {
     632     8349933 :     return argument;
     633             :   }
     634             : }
     635             : 
     636      250813 : void Tools::interpretRanges(std::vector<std::string>&s) {
     637      250813 :   std::vector<std::string> news;
     638     1260020 :   for(const auto & p :s) {
     639     1009207 :     news.push_back(p);
     640             :     // the separating dash is searched from the second character, so that the first
     641             :     // end of the range can be negative (e.g. -10-5 or -10--1, used for residue numbers)
     642     1009207 :     size_t dash=p.find('-',1);
     643     1009207 :     if(dash==std::string::npos) {
     644     1006962 :       continue;
     645             :     }
     646             :     int first;
     647        6156 :     if(!Tools::convertToAny(p.substr(0,dash),first)) {
     648         833 :       continue;
     649             :     }
     650        2245 :     int stride=1;
     651             :     int second;
     652        2245 :     size_t colon=p.substr(dash+1).find(":");
     653        2245 :     if(colon!=std::string::npos) {
     654         234 :       if(!Tools::convertToAny(p.substr(dash+1).substr(0,colon),second) ||
     655         312 :           !Tools::convertToAny(p.substr(dash+1).substr(colon+1),stride)) {
     656           0 :         continue;
     657             :       }
     658             :     } else {
     659        4334 :       if(!Tools::convertToAny(p.substr(dash+1),second)) {
     660           0 :         continue;
     661             :       }
     662             :     }
     663        2245 :     news.resize(news.size()-1);
     664        2245 :     if(first<=second) {
     665        2244 :       plumed_massert(stride>0,"interpreting ranges "+ p + ", stride should be positive");
     666      625942 :       for(int i=first; i<=second; i+=stride) {
     667             :         std::string ss;
     668      623698 :         convert(i,ss);
     669      623698 :         news.push_back(ss);
     670             :       }
     671             :     } else {
     672           1 :       plumed_massert(stride<0,"interpreting ranges "+ p + ", stride should be positive");
     673           3 :       for(int i=first; i>=second; i+=stride) {
     674             :         std::string ss;
     675           2 :         convert(i,ss);
     676           2 :         news.push_back(ss);
     677             :       }
     678             :     }
     679             :   }
     680      250813 :   s=news;
     681      250813 : }
     682             : 
     683       89363 : void Tools::interpretLabel(std::vector<std::string>&s) {
     684       89363 :   if(s.size()<2) {
     685         459 :     return;
     686             :   }
     687       88904 :   std::string s0=s[0];
     688       88904 :   unsigned l=s0.length();
     689       88904 :   if(l<1) {
     690             :     return;
     691             :   }
     692       88904 :   if(s0[l-1]==':') {
     693             :     s[0]=s[1];
     694      119980 :     s[1]="LABEL="+s0.substr(0,l-1);
     695             :   }
     696             :   std::transform(s[0].begin(), s[0].end(), s[0].begin(), ::toupper);
     697             : }
     698             : 
     699       12331 : std::vector<std::string> Tools::ls(const std::string&d) {
     700       12331 :   std::vector<std::string> result;
     701      199110 :   for (auto const& dir_entry : std::filesystem::directory_iterator{d}) {
     702      324234 :     result.push_back(dir_entry.path().filename().string());
     703             :   }
     704       12331 :   return result;
     705           0 : }
     706             : 
     707        4627 : void Tools::stripLeadingAndTrailingBlanks( std::string& str ) {
     708             :   std::size_t first=str.find_first_not_of(' ');
     709        4627 :   std::size_t last=str.find_last_not_of(' ');
     710        4627 :   if( first<=last && first!=std::string::npos) {
     711        9180 :     str=str.substr(first,last+1);
     712             :   }
     713        4627 : }
     714             : 
     715       22062 : std::string Tools::extension(const std::string&s) {
     716       22062 :   size_t n=s.find_last_of(".");
     717             :   std::string ext;
     718       22062 :   if(n!=std::string::npos && n+1<s.length() && n+5>=s.length()) {
     719       15338 :     ext=s.substr(n+1);
     720       15338 :     if(ext.find("/")!=std::string::npos) {
     721             :       ext="";
     722             :     }
     723       15338 :     std::string base=s.substr(0,n);
     724       15338 :     if(base.length()==0) {
     725             :       ext="";
     726             :     }
     727       15338 :     if(base.length()>0 && base[base.length()-1]=='/') {
     728             :       ext="";
     729             :     }
     730             :   }
     731       22062 :   return ext;
     732             : }
     733             : 
     734           0 : double Tools::bessel0( const double& val ) {
     735           0 :   if (std::abs(val)<3.75) {
     736           0 :     double y = Tools::fastpow( val/3.75, 2 );
     737           0 :     return 1 + y*(3.5156229 +y*(3.0899424 + y*(1.2067492+y*(0.2659732+y*(0.0360768+y*0.0045813)))));
     738             :   }
     739           0 :   double ax=std::abs(val), y=3.75/ax, bx=std::exp(ax)/std::sqrt(ax);
     740           0 :   ax=0.39894228+y*(0.01328592+y*(0.00225319+y*(-0.00157565+y*(0.00916281+y*(-0.02057706+y*(0.02635537+y*(-0.01647633+y*0.00392377)))))));
     741           0 :   return ax*bx;
     742             : }
     743             : 
     744     9801575 : bool Tools::startWith(std::string_view full, std::string_view start) {
     745     9801575 :   return 0==full.compare(0,start.size(),start);
     746             : }
     747             : 
     748        7494 : bool Tools::findKeyword(const std::vector<std::string>&line,const std::string&key) {
     749       21188 :   for(const auto & p : line) {
     750       16956 :     if(startWith(p,key)
     751       16956 :         &&p.size()>key.size()) {
     752             :       //this does not allocate a new string `key+"="`
     753        3262 :       if(p[key.size()]=='=') {
     754             :         return true;
     755             :       }
     756             :     }
     757             :   }
     758             :   return false;
     759             : }
     760             : 
     761         522 : Tools::DirectoryChanger::DirectoryChanger(const char*path):
     762         522 :   originalpath(std::filesystem::current_path()) {
     763         522 :   if(!path) {
     764             :     return;
     765             :   }
     766         522 :   if(std::strlen(path)==0) {
     767             :     return;
     768             :   }
     769             :   try {
     770           4 :     std::filesystem::current_path(path);
     771           2 :   } catch(std::filesystem::filesystem_error & e) {
     772           4 :     plumed_error() << "cannot change directory to " << path << ": " << e.code().message();
     773           2 :   }
     774             : }
     775             : 
     776         520 : Tools::DirectoryChanger::~DirectoryChanger() {
     777             :   try {
     778         520 :     std::filesystem::current_path(originalpath);
     779           0 :   } catch(std::filesystem::filesystem_error & e) {
     780           0 :     std::fprintf(stderr,"+++ WARNING: cannot cd back to directory %s\n",originalpath.c_str());
     781           0 :   }
     782         520 : }
     783             : 
     784       23928 : std::unique_ptr<std::lock_guard<std::mutex>> Tools::molfile_lock() {
     785             :   static std::mutex mtx;
     786       23928 :   return Tools::make_unique<std::lock_guard<std::mutex>>(mtx);
     787             : }
     788             : 
     789             : /// Internal tool, I am keeping it private for now
     790             : namespace {
     791             : 
     792             : class process_one_exception {
     793             :   //To future me/you: this needs to remain called "msg" because otherwise
     794             :   //it will break the call message processing in `process_all_exceptions`
     795             :   std::string & msg;
     796             :   bool first=true;
     797         695 :   void update() {
     798         695 :     if(!first) {
     799         114 :       msg+="\n\nThe above exception was the direct cause of the following exception:\n";
     800             :     }
     801         695 :     first=false;
     802         695 :   }
     803             : public:
     804         581 :   process_one_exception(std::string & inmsg):
     805         581 :     msg(inmsg)
     806             :   {}
     807         694 :   void operator()(const std::exception & e) {
     808         694 :     update();
     809         694 :     msg+=e.what();
     810         694 :   }
     811           0 :   void operator()(const std::string & e) {
     812           0 :     update();
     813           0 :     msg+=e;
     814           0 :   }
     815           1 :   void operator()(const char* e) {
     816           1 :     update();
     817           1 :     msg+=e;
     818           1 :   }
     819             : };
     820             : 
     821             : template<class T>
     822         695 : static void process_all_exceptions(T&& f) {
     823             :   try {
     824             :     // First throw the current exception
     825         695 :     throw;
     826         809 :   } catch(const std::nested_exception & e) {
     827             :     // If nested, we go recursive
     828             :     // notice that we apply function f only if exception is also a std::exception
     829             :     try {
     830         114 :       e.rethrow_nested();
     831         228 :     } catch(...) {
     832         114 :       process_all_exceptions(f);
     833             :     }
     834         114 :     auto d=dynamic_cast<const std::exception*>(&e);
     835         114 :     if(d) {
     836         114 :       f(*d);
     837             :     }
     838        1160 :   } catch(const std::exception &e) {
     839             :     // If not nested, we end recursion
     840         580 :     f(e);
     841           0 :   } catch(const std::string &e) {
     842             :     // If not nested, we end recursion
     843           0 :     f(e);
     844           2 :   } catch(const char* e) {
     845             :     // If not nested, we end recursion
     846           1 :     f(e);
     847           0 :   } catch(...) {
     848             :     // If not nested and of unknown type, we stop the chain
     849             :   }
     850         695 : }
     851             : 
     852             : }
     853             : 
     854         581 : std::string Tools::concatenateExceptionMessages() {
     855             :   std::string msg;
     856         581 :   process_all_exceptions(process_one_exception(msg));
     857         581 :   return msg;
     858             : }
     859             : 
     860          50 : std::string Tools::convertRegexForJson (const std::string& command) {
     861          50 :   std::stringstream ss;
     862          50 :   ss << std::quoted(command);
     863             :   std::string modifiedCommand=ss.str();
     864             :   //std::quoted quotes, and I do not want the extra delimiters
     865         100 :   return modifiedCommand.substr(1,modifiedCommand.length()-2);
     866          50 : }
     867             : }

Generated by: LCOV version 1.16