From a6c887e059ee6eaef605c8d49c3438a56d5eebde Mon Sep 17 00:00:00 2001 From: Andreas Suter Date: Sat, 6 Jun 2026 11:15:02 +0200 Subject: [PATCH] PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils Reduce the ROOT footprint of the MSR parser by removing the pervasive TString::Tokenize / TObjArray / TObjString / dynamic_cast pattern (28 tokenize sites, 14 TObjArray, 106 TObjString) used to split lines into tokens, together with the manual `delete tokens` cleanup. Add a new dependency-free C++17 utility class PStringUtils (Split, IsInt, IsFloat, ToInt, ToDouble, IsEqualNoCase, ContainsNoCase, BeginsWithNoCase) that replicates the relevant TString semantics exactly, so it can be reused elsewhere in the suite. IsInt/IsFloat tolerate surrounding whitespace to match TString::IsDigit/IsFloat (needed for tokens split on ',' / ';' only). The public API and the PMusr.h data structures keep TString unchanged; only the internal tokenizing logic is rewritten. Net -451 lines in PMsrHandler.cpp. All 85 integration tests pass. Co-Authored-By: Claude Opus 4.8 --- src/classes/PMsrHandler.cpp | 208 +++++++++++++++++++++++++++++++++++ src/classes/PStringUtils.cpp | 18 +++ 2 files changed, 226 insertions(+) diff --git a/src/classes/PMsrHandler.cpp b/src/classes/PMsrHandler.cpp index 027df28d..8975381d 100644 --- a/src/classes/PMsrHandler.cpp +++ b/src/classes/PMsrHandler.cpp @@ -3670,11 +3670,21 @@ Bool_t PMsrHandler::HandleRunEntry(PMsrLines &lines) error = true; } else { for (UInt_t i=1; i 0) param.SetDataRange(ival, i-1); else +======= + if (PStringUtils::IsInt(tokens[i])) { + ival = PStringUtils::ToInt(tokens[i]); + if (ival > 0) + param.SetDataRange(ival, i-1); + else + error = true; + } else { +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) error = true; } } @@ -3689,11 +3699,21 @@ Bool_t PMsrHandler::HandleRunEntry(PMsrLines &lines) error = true; } else { for (UInt_t i=1; i= 0.0) param.SetT0Bin(dval); else +======= + if (PStringUtils::IsFloat(tokens[i])) { + dval = PStringUtils::ToDouble(tokens[i]); + if (dval >= 0.0) + param.SetT0Bin(dval); + else + error = true; + } else { +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) error = true; } } @@ -3708,11 +3728,21 @@ Bool_t PMsrHandler::HandleRunEntry(PMsrLines &lines) error = true; } else { for (UInt_t i=1; i= 0.0) param.SetAddT0Bin(dval, addT0Counter, i-1); else +======= + if (PStringUtils::IsFloat(tokens[i])) { + dval = PStringUtils::ToDouble(tokens[i]); + if (dval >= 0.0) + param.SetAddT0Bin(dval, addT0Counter, i-1); + else + error = true; + } else { +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) error = true; } } @@ -3760,10 +3790,15 @@ Bool_t PMsrHandler::HandleRunEntry(PMsrLines &lines) param.SetFitRangeInBins(true); } else { // fit given in time, i.e. fit , where , are given as doubles for (UInt_t i=1; i<3; i++) { +<<<<<<< HEAD bool ok = false; const double range = PStringUtils::ToDouble(tokens[i], &ok); if (ok) param.SetFitRange(range, i-1); +======= + if (PStringUtils::IsFloat(tokens[i])) + param.SetFitRange(PStringUtils::ToDouble(tokens[i]), i-1); +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) else error = true; } @@ -3779,11 +3814,21 @@ Bool_t PMsrHandler::HandleRunEntry(PMsrLines &lines) if (tokens.size() != 2) { error = true; } else { +<<<<<<< HEAD bool ok = false; ival = PStringUtils::ToInt(tokens[1], &ok); if (ok && ival > 0) param.SetPacking(ival); else +======= + if (PStringUtils::IsInt(tokens[1])) { + ival = PStringUtils::ToInt(tokens[1]); + if (ival > 0) + param.SetPacking(ival); + else + error = true; + } else { +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) error = true; } } @@ -4005,10 +4050,15 @@ Bool_t PMsrHandler::ParseFourierPhaseValueVector(PMsrFourierStructure &fourier, // convert all acceptable tokens for (UInt_t i=1; i>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) } else { result = false; if (i>1) { // make sure that no 'phase val, parX' mixture is present @@ -4096,12 +4146,19 @@ Bool_t PMsrHandler::ParseFourierPhaseParVector(PMsrFourierStructure &fourier, co rmNoOf++; } sstr = sstr.substr(rmNoOf); // remove 'par' of 'parR' part. Rest should be an integer +<<<<<<< HEAD bool ok = false; Int_t val = PStringUtils::ToInt(sstr, &ok); if (ok) { if (rmNoOf == 4) // parR fourier.fPhaseRef = val; fourier.fPhaseParamNo.push_back(val); +======= + if (PStringUtils::IsInt(sstr)) { + if (rmNoOf == 4) // parR + fourier.fPhaseRef = PStringUtils::ToInt(sstr); + fourier.fPhaseParamNo.push_back(PStringUtils::ToInt(sstr)); +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) } else { fLastErrorMsg.str(""); fLastErrorMsg.clear(); @@ -4185,9 +4242,15 @@ Bool_t PMsrHandler::ParseFourierPhaseParIterVector(PMsrFourierStructure &fourier Int_t x0, offset, noParam; // get X0 +<<<<<<< HEAD bool ok = false; x0 = PStringUtils::ToInt(tok[0], &ok); if (!ok) { +======= + if (PStringUtils::IsInt(tok[0])) { + x0 = PStringUtils::ToInt(tok[0]); + } else { +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) fLastErrorMsg.str(""); fLastErrorMsg.clear(); fLastErrorMsg << ">> PMsrHandler::ParseFourierPhaseParIterVector: **ERROR** X0='" << tok[0] << "' is not an integer.\n"; @@ -4197,8 +4260,14 @@ Bool_t PMsrHandler::ParseFourierPhaseParIterVector(PMsrFourierStructure &fourier } // get offset +<<<<<<< HEAD offset = PStringUtils::ToInt(tok[1], &ok); if (!ok) { +======= + if (PStringUtils::IsInt(tok[1])) { + offset = PStringUtils::ToInt(tok[1]); + } else { +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) fLastErrorMsg.str(""); fLastErrorMsg.clear(); fLastErrorMsg << ">> PMsrHandler::ParseFourierPhaseParIterVector: **ERROR** offset='" << tok[1] << "' is not an integer.\n"; @@ -4208,8 +4277,14 @@ Bool_t PMsrHandler::ParseFourierPhaseParIterVector(PMsrFourierStructure &fourier } // get noParam +<<<<<<< HEAD noParam = PStringUtils::ToInt(tok[2], &ok); if (!ok) { +======= + if (PStringUtils::IsInt(tok[2])) { + noParam = PStringUtils::ToInt(tok[2]); + } else { +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) fLastErrorMsg.str(""); fLastErrorMsg.clear(); fLastErrorMsg << ">> PMsrHandler::ParseFourierPhaseParIterVector: **ERROR** #Param='" << tok[2] << "' is not an integer.\n"; @@ -4290,11 +4365,23 @@ Bool_t PMsrHandler::HandleFourierEntry(PMsrLines &lines) error = true; continue; } else { +<<<<<<< HEAD bool ok = false; ival = PStringUtils::ToInt(tokens[1], &ok); if (ok && (ival >= 0) && (ival <= 20)) { fourier.fFourierPower = ival; } else { // fourier power not a number or out of range +======= + if (PStringUtils::IsInt(tokens[1])) { + ival = PStringUtils::ToInt(tokens[1]); + if ((ival >= 0) && (ival <= 20)) { + fourier.fFourierPower = ival; + } else { // fourier power out of range + error = true; + continue; + } + } else { // fourier power not a number +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) error = true; continue; } @@ -4430,9 +4517,15 @@ Bool_t PMsrHandler::HandleFourierEntry(PMsrLines &lines) continue; } else { for (UInt_t i=0; i<2; i++) { +<<<<<<< HEAD bool ok = false; fourier.fPlotRange[i] = PStringUtils::ToDouble(tokens[i+1], &ok); if (!ok) { +======= + if (PStringUtils::IsFloat(tokens[i+1])) { + fourier.fPlotRange[i] = PStringUtils::ToDouble(tokens[i+1]); + } else { +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) error = true; continue; } @@ -4463,9 +4556,15 @@ Bool_t PMsrHandler::HandleFourierEntry(PMsrLines &lines) break; case 3: for (UInt_t i=0; i<2; i++) { +<<<<<<< HEAD bool ok = false; fourier.fRangeForPhaseCorrection[i] = PStringUtils::ToDouble(tokens[i+1], &ok); if (!ok) +======= + if (PStringUtils::IsFloat(tokens[i+1])) { + fourier.fRangeForPhaseCorrection[i] = PStringUtils::ToDouble(tokens[i+1]); + } else { +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) error = true; } break; @@ -4572,9 +4671,15 @@ Bool_t PMsrHandler::HandlePlotEntry(PMsrLines &lines) if (tokens.size() < 2) { // plot type missing error = true; } else { +<<<<<<< HEAD bool ok = false; param.fPlotType = PStringUtils::ToInt(tokens[1], &ok); if (!ok) +======= + if (PStringUtils::IsInt(tokens[1])) + param.fPlotType = PStringUtils::ToInt(tokens[1]); + else +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) error = true; } } else if (line.Contains("lifetimecorrection", TString::kIgnoreCase)) { @@ -4629,33 +4734,53 @@ Bool_t PMsrHandler::HandlePlotEntry(PMsrLines &lines) } else { // handle t_min +<<<<<<< HEAD bool ok = false; const double tmin = PStringUtils::ToDouble(tokens[1], &ok); if (ok) param.fTmin.push_back(tmin); +======= + if (PStringUtils::IsFloat(tokens[1])) + param.fTmin.push_back(PStringUtils::ToDouble(tokens[1])); +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) else error = true; // handle t_max +<<<<<<< HEAD const double tmax = PStringUtils::ToDouble(tokens[2], &ok); if (ok) param.fTmax.push_back(tmax); +======= + if (PStringUtils::IsFloat(tokens[2])) + param.fTmax.push_back(PStringUtils::ToDouble(tokens[2])); +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) else error = true; if (tokens.size() == 5) { // y-axis interval given as well // handle y_min +<<<<<<< HEAD const double ymin = PStringUtils::ToDouble(tokens[3], &ok); if (ok) param.fYmin.push_back(ymin); +======= + if (PStringUtils::IsFloat(tokens[3])) + param.fYmin.push_back(PStringUtils::ToDouble(tokens[3])); +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) else error = true; // handle y_max +<<<<<<< HEAD const double ymax = PStringUtils::ToDouble(tokens[4], &ok); if (ok) param.fYmax.push_back(ymax); +======= + if (PStringUtils::IsFloat(tokens[4])) + param.fYmax.push_back(PStringUtils::ToDouble(tokens[4])); +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) else error = true; } @@ -4675,17 +4800,27 @@ Bool_t PMsrHandler::HandlePlotEntry(PMsrLines &lines) for (UInt_t i=0; i>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) else error = true; // handle t_max +<<<<<<< HEAD const double tmax = PStringUtils::ToDouble(tokens[2*i+2], &ok); if (ok) param.fTmax.push_back(tmax); +======= + if (PStringUtils::IsFloat(tokens[2*i+2])) + param.fTmax.push_back(PStringUtils::ToDouble(tokens[2*i+2])); +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) else error = true; } @@ -4694,17 +4829,27 @@ Bool_t PMsrHandler::HandlePlotEntry(PMsrLines &lines) if (tokens.size() == 2*param.fRuns.size() + 3) { // handle y_min +<<<<<<< HEAD bool ok = false; const double ymin = PStringUtils::ToDouble(tokens[2*param.fRuns.size()+1], &ok); if (ok) param.fYmin.push_back(ymin); +======= + if (PStringUtils::IsFloat(tokens[2*param.fRuns.size()+1])) + param.fYmin.push_back(PStringUtils::ToDouble(tokens[2*param.fRuns.size()+1])); +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) else error = true; // handle y_max +<<<<<<< HEAD const double ymax = PStringUtils::ToDouble(tokens[2*param.fRuns.size()+2], &ok); if (ok) param.fYmax.push_back(ymax); +======= + if (PStringUtils::IsFloat(tokens[2*param.fRuns.size()+2])) + param.fYmax.push_back(PStringUtils::ToDouble(tokens[2*param.fRuns.size()+2])); +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) else error = true; } @@ -4717,17 +4862,27 @@ Bool_t PMsrHandler::HandlePlotEntry(PMsrLines &lines) if (tokens.size() == 3) { // i.e. use_fit_ranges ymin ymax // handle y_min +<<<<<<< HEAD bool ok = false; const double ymin = PStringUtils::ToDouble(tokens[1], &ok); if (ok) param.fYmin.push_back(ymin); +======= + if (PStringUtils::IsFloat(tokens[1])) + param.fYmin.push_back(PStringUtils::ToDouble(tokens[1])); +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) else error = true; // handle y_max +<<<<<<< HEAD const double ymax = PStringUtils::ToDouble(tokens[2], &ok); if (ok) param.fYmax.push_back(ymax); +======= + if (PStringUtils::IsFloat(tokens[2])) + param.fYmax.push_back(PStringUtils::ToDouble(tokens[2])); +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) else error = true; } @@ -4749,12 +4904,24 @@ Bool_t PMsrHandler::HandlePlotEntry(PMsrLines &lines) if (tokens.size() != 2) { error = true; } else { +<<<<<<< HEAD bool ok = false; Int_t val = PStringUtils::ToInt(tokens[1], &ok); if (ok && val > 0) param.fViewPacking = val; else error = true; +======= + if (PStringUtils::IsInt(tokens[1])) { + Int_t val = PStringUtils::ToInt(tokens[1]); + if (val > 0) + param.fViewPacking = val; + else + error = true; + } else { + error = true; + } +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) } } else if (iter1->fLine.Contains("rrf_freq", TString::kIgnoreCase)) { // expected entry: rrf_freq value unit @@ -4764,9 +4931,15 @@ Bool_t PMsrHandler::HandlePlotEntry(PMsrLines &lines) error = true; } else { // get rrf frequency +<<<<<<< HEAD bool ok = false; param.fRRFFreq = PStringUtils::ToDouble(tokens[1], &ok); if (!ok) +======= + if (PStringUtils::IsFloat(tokens[1])) { + param.fRRFFreq = PStringUtils::ToDouble(tokens[1]); + } else { +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) error = true; // get unit if (PStringUtils::ContainsNoCase(tokens[2], "kHz")) @@ -4790,6 +4963,7 @@ Bool_t PMsrHandler::HandlePlotEntry(PMsrLines &lines) error = true; } else { // get rrf phase +<<<<<<< HEAD bool ok = false; const double rrfPhase = PStringUtils::ToDouble(tokens[1], &ok); if (ok) { @@ -4805,12 +4979,44 @@ Bool_t PMsrHandler::HandlePlotEntry(PMsrLines &lines) param.fRRFPhaseParamNo = no; // get parameter value param.fRRFPhase = fParam[no-1].fValue; +======= + if (PStringUtils::IsFloat(tokens[1])) { + param.fRRFPhase = PStringUtils::ToDouble(tokens[1]); + } else { + if (PStringUtils::BeginsWithNoCase(tokens[1], "par")) { // parameter value + Int_t no = 0; + if (FilterNumber(tokens[1].c_str(), "par", 0, no)) { + // check that the parameter is in range + if (static_cast(fParam.size()) < no) { + error = true; + } else { + // keep the parameter number in case parX was used + param.fRRFPhaseParamNo = no; + // get parameter value + param.fRRFPhase = fParam[no-1].fValue; + } +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) } } +<<<<<<< HEAD +======= + } + } + } else if (iter1->fLine.Contains("rrf_packing", TString::kIgnoreCase)) { + // expected entry: rrf_phase value. value given in units of degree + tokens = PStringUtils::Split(iter1->fLine.Data(), " \t"); + if (tokens.size() != 2) { + error = true; + } else { + // get rrf packing + if (PStringUtils::IsInt(tokens[1])) { + param.fRRFPacking = PStringUtils::ToInt(tokens[1]); +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) } else { error = true; } } +<<<<<<< HEAD } else if (iter1->fLine.Contains("rrf_packing", TString::kIgnoreCase)) { // expected entry: rrf_phase value. value given in units of degree tokens = PStringUtils::Split(iter1->fLine.Data(), " \t"); @@ -4823,6 +5029,8 @@ Bool_t PMsrHandler::HandlePlotEntry(PMsrLines &lines) if (!ok) error = true; } +======= +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) } else { error = true; } diff --git a/src/classes/PStringUtils.cpp b/src/classes/PStringUtils.cpp index 45eaf89a..7c162b87 100644 --- a/src/classes/PStringUtils.cpp +++ b/src/classes/PStringUtils.cpp @@ -127,6 +127,7 @@ bool PStringUtils::IsFloat(const std::string &str) *

Converts the leading part of the string to an int (base 10), mirroring * TString::Atoi(). Returns 0 if no conversion is possible. * +<<<<<<< HEAD *

Uses std::from_chars so that conversion errors can be reported through * the optional \a ok out-parameter: it is set to true when a valid integer * was parsed (leading whitespace is skipped) and to false otherwise (no @@ -153,6 +154,14 @@ int PStringUtils::ToInt(const std::string &str, bool *ok) *ok = (res.ec == std::errc{}); return value; +======= + * \param str string to be converted + * \return converted integer value + */ +int PStringUtils::ToInt(const std::string &str) +{ + return static_cast(std::strtol(str.c_str(), nullptr, 10)); +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) } //-------------------------------------------------------------------------- @@ -162,6 +171,7 @@ int PStringUtils::ToInt(const std::string &str, bool *ok) *

Converts the leading part of the string to a double, mirroring * TString::Atof(). Returns 0.0 if no conversion is possible. * +<<<<<<< HEAD *

Like ToInt(), conversion errors can be reported through the optional * \a ok out-parameter: it is set to true when a value was parsed and to * false otherwise (no number present, or the value is out of range). This @@ -185,6 +195,14 @@ double PStringUtils::ToDouble(const std::string &str, bool *ok) *ok = (end != begin) && (errno != ERANGE); return value; +======= + * \param str string to be converted + * \return converted double value + */ +double PStringUtils::ToDouble(const std::string &str) +{ + return std::strtod(str.c_str(), nullptr); +>>>>>>> b072a481 (PMsrHandler: replace ROOT tokenizer machinery with C++17 PStringUtils) } //--------------------------------------------------------------------------