Mantid
Loading...
Searching...
No Matches
MultiFileNameParser.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
7//----------------------------------------------------------------------
8// Includes
9//----------------------------------------------------------------------
11
15
16#include <numeric>
17#include <sstream>
18
19#include <boost/algorithm/string.hpp>
20#include <boost/regex.hpp>
21#include <utility>
22
25// Static constants.
27
28namespace Regexs {
29const std::string INST = "([A-Za-z]+|PG3|pg3)";
30
31const std::string UNDERSCORE = "(_{0,1})";
32const std::string SPACE = "(\\s*)";
33
34const std::string COMMA = "(" + SPACE + "," + SPACE + ")";
35const std::string PLUS = "(" + SPACE + "\\+" + SPACE + ")";
36const std::string MINUS = "(" + SPACE + "\\-" + SPACE + ")";
37const std::string COLON = "(" + SPACE + ":" + SPACE + ")";
38
39const std::string SINGLE = "(" + INST + "*[0-9]+)";
40const std::string RANGE = "(" + SINGLE + COLON + SINGLE + ")";
41const std::string STEP_RANGE = "(" + SINGLE + COLON + SINGLE + COLON + SINGLE + ")";
42const std::string ADD_RANGE = "(" + SINGLE + MINUS + SINGLE + ")";
43const std::string ADD_STEP_RANGE = "(" + SINGLE + MINUS + SINGLE + COLON + SINGLE + ")";
44const std::string SINGLE_OR_STEP_OR_ADD_RANGE = "(" + ADD_STEP_RANGE + "|" + ADD_RANGE + "|" + SINGLE + ")";
45const std::string ADD_LIST = "(" + SINGLE_OR_STEP_OR_ADD_RANGE + "(" + PLUS + SINGLE_OR_STEP_OR_ADD_RANGE + ")+" + ")";
46
47const std::string ANY =
48 "(" + ADD_STEP_RANGE + "|" + ADD_LIST + "|" + ADD_RANGE + "|" + STEP_RANGE + "|" + RANGE + "|" + SINGLE + ")";
49const std::string LIST = "(" + ANY + "(" + COMMA + ANY + ")*" + ")";
50} // namespace Regexs
51
53// Forward declarations.
55
56namespace {
57// Anonymous helper functions.
58void parseToken(std::vector<std::vector<unsigned int>> &parsedRuns, const std::string &token);
59std::vector<std::vector<unsigned int>> generateRange(const unsigned int from, const unsigned int to,
60 const unsigned int stepSize, const bool addRuns);
61void validateToken(const std::string &token);
62bool matchesFully(const std::string &stringToMatch, const std::string &regexString, const bool caseless = false);
63std::string getMatchingString(const std::string &regexString, const std::string &toParse, const bool caseless = false);
64std::string pad(const unsigned int run, const std::string &instString);
65
66std::set<std::pair<unsigned int, unsigned int>> &
67mergeAdjacentRanges(std::set<std::pair<unsigned int, unsigned int>> &ranges,
68 const std::pair<unsigned int, unsigned int> &range);
69
70// Helper functor.
71struct RangeContainsRun {
72 bool operator()(const std::pair<unsigned int, unsigned int> &range, const unsigned int run);
73 bool operator()(const unsigned int run, const std::pair<unsigned int, unsigned int> &range);
74};
75
76std::string toString(const RunRangeList &runRangeList);
77std::string &accumulateString(std::string &output, std::pair<unsigned int, unsigned int> runRange);
78} // namespace
79
81// Scoped, global functions.
83
96std::string suggestWorkspaceName(const std::vector<std::string> &fileNames) {
97 Parser parser;
98 RunRangeList runs;
99
100 // For each file name, parse the run number out of it, and add it to a
101 // RunRangeList.
102 for (const auto &fileName : fileNames) {
103 parser.parse(fileName);
104 runs.addRun(parser.runs()[0][0]);
105 }
106
107 // Return the suggested ws name.
108 return parser.instString() + parser.underscoreString() + toString(runs);
109}
110
112// Comparator class.
114
121bool ReverseCaselessCompare::operator()(const std::string &a, const std::string &b) const {
122 std::string lowerA;
123 lowerA.resize(a.size());
124 std::string lowerB;
125 lowerB.resize(b.size());
126
127 std::transform(a.cbegin(), a.cend(), lowerA.begin(), tolower);
128 std::transform(b.cbegin(), b.cend(), lowerB.begin(), tolower);
129
130 return lowerA > lowerB;
131}
132
134// Public member functions of Parser class.
136
139 : m_runs(), m_fileNames(), m_multiFileName(), m_dirString(), m_instString(), m_underscoreString(), m_runString(),
140 m_extString(), m_validInstNames(), m_trimWhiteSpaces(true) {
142
143 const auto facilities = config.getFacilities();
144 for (const auto facility : facilities) {
145 const std::vector<InstrumentInfo> instruments = facility->instruments();
146
147 for (const auto &instrument : instruments) {
148 m_validInstNames.insert(instrument.name());
149 m_validInstNames.insert(instrument.shortName());
150 }
151 }
152}
153
161void Parser::parse(const std::string &multiFileName) {
162 // Clear any contents of the member variables.
163 clear();
164
165 // Set the string to parse.
166 m_multiFileName = multiFileName;
167
168 // Split the string to be parsed into sections, and do some validation.
169 split();
170
171 // Parse the run section into unsigned ints we can use.
173
174 // Set up helper functor.
176
177 // Generate complete file names for each run using helper functor.
178 std::transform(m_runs.begin(), m_runs.end(), std::back_inserter(m_fileNames), generateFileName);
179}
180
192std::vector<std::vector<unsigned int>> Parser::parseMultiRunString(std::string runString) {
193 // If the run string is empty, return no runs.
194 if (runString.empty())
195 return std::vector<std::vector<unsigned int>>();
196
197 // Remove whitespaces if requested.
198 if (trimWhiteSpaces()) {
199 runString.erase(std::remove_if( // ("Erase-remove" idiom.)
200 runString.begin(), runString.end(), isspace),
201 runString.end());
202 }
203 // Only numeric characters, or occurances of plus, minus, comma and colon are
204 // allowed.
205 if (!matchesFully(runString, "([0-9]|\\+|\\-|,|:)+")) {
206 throw std::runtime_error("Non-numeric or otherwise unaccetable character(s) detected.");
207 }
208
209 // Tokenize on commas.
210 std::vector<std::string> tokens;
211 tokens = boost::split(tokens, runString, boost::is_any_of(","));
212
213 // Validate each token.
214 std::for_each(tokens.begin(), tokens.end(), validateToken);
215
216 // Parse each token, accumulate the results, and return them.
217 std::vector<std::vector<unsigned int>> runGroups;
218 for (auto const &token : tokens) {
219 parseToken(runGroups, token);
220 }
221 return runGroups;
222}
223
230
237void Parser::setTrimWhiteSpaces(const bool &setting) { m_trimWhiteSpaces = setting; }
238
240// Private member functions of Parser class.
242
247 m_runs.clear();
248 m_fileNames.clear();
249 m_multiFileName.clear();
250 m_dirString.clear();
251 m_instString.clear();
252 m_underscoreString.clear();
253 m_runString.clear();
254 m_extString.clear();
255}
256
266 if (m_multiFileName.empty())
267 throw std::runtime_error("No file name to parse.");
268
269 // (We shun the use of Poco::File here as it is unable to deal with certain
270 // combinations of special characters, for example double commas.)
271
272 // Clear whitespace before getting extentions and directories, if requested.
273 if (trimWhiteSpaces()) {
274 m_multiFileName.erase(std::remove_if( // ("Erase-remove" idiom.)
275 m_multiFileName.begin(), m_multiFileName.end(), isspace),
276 m_multiFileName.end());
277 }
278 // Get the extension, if there is one.
279 const size_t lastDot = m_multiFileName.find_last_of('.');
280 if (lastDot != std::string::npos)
281 m_extString = m_multiFileName.substr(lastDot);
282
283 // Get the directory, if there is one.
284 const size_t lastSeparator = m_multiFileName.find_last_of("/\\");
285 if (lastSeparator != std::string::npos)
286 m_dirString = m_multiFileName.substr(0, lastSeparator + 1);
287
288 // If the directory contains an instance of a comma, then the string is
289 // a comma separated list of single *full* file names to load.
290 if (std::string::npos != m_dirString.find(','))
291 throw std::runtime_error("Unable to parse.");
292
293 // Slice off the directory and extension.
294 std::string base =
295 m_multiFileName.substr(m_dirString.size(), m_multiFileName.size() - (m_dirString.size() + m_extString.size()));
296
297 if (base.empty())
298 throw std::runtime_error("There does not appear to be any runs present.");
299
300 auto instrumentNameIt = std::find_if(m_validInstNames.cbegin(), m_validInstNames.cend(), [&base](const auto &name) {
301 return matchesFully(base, name + ".*", true);
302 }); // USE CASELESS MATCHES HERE.
303
304 // See if the user has typed in one of the available instrument names.
305 if (instrumentNameIt != m_validInstNames.cend()) {
306 m_instString = getMatchingString("^" + *instrumentNameIt, base, true);
307 }
308
309 // If not, use the default, or throw if we encounter an unrecognisable
310 // non-numeric string.
311 if (m_instString.empty()) {
312 if (base.empty())
313 throw std::runtime_error("There does not appear to be any runs present.");
314
315 if (isdigit(base[0]))
316 m_instString = ConfigService::Instance().getString("default.instrument");
317 else
318 throw std::runtime_error("There does not appear to be a valid instrument name present.");
319 } else {
320 // Chop off instrument name.
321 base = base.substr(m_instString.size(), base.size());
322 }
323
324 if (base.empty())
325 throw std::runtime_error("There does not appear to be any runs present.");
326
327 const auto &instInfo = ConfigService::Instance().getInstrument(m_instString);
328 // why?
329 // m_instString = instInfo.shortName(); // Make sure we're using the shortened
330 // form of the isntrument name.
331
332 if (boost::starts_with(base, instInfo.delimiter())) {
333 // Store the instrument delimiter, and strip it off the start of the string.
334 m_underscoreString = instInfo.delimiter();
335 base = base.substr(m_underscoreString.size(), base.size());
336 }
337
338 m_runString = getMatchingString("^" + Regexs::LIST, base);
339
340 if (m_runString.size() != base.size()) {
341 throw std::runtime_error("There is an unparsable token present.");
342 }
343}
344
346// Helper functor.
348
356GenerateFileName::GenerateFileName(std::string prefix, std::string suffix, std::string instString)
357 : m_prefix(std::move(prefix)), m_suffix(std::move(suffix)), m_instString(std::move(instString)) {}
358
367std::vector<std::string> GenerateFileName::operator()(const std::vector<unsigned int> &runs) {
368 std::vector<std::string> fileNames;
369
370 std::transform(runs.begin(), runs.end(), std::back_inserter(fileNames),
371 (*this) // Call other overloaded function operator.
372 );
373
374 return fileNames;
375}
376
384std::string GenerateFileName::operator()(const unsigned int run) {
385 std::stringstream fileName;
386
387 fileName << m_prefix << pad(run, m_instString) << m_suffix;
388
389 return fileName.str();
390}
391
393// Public member functions of RunRangeList class.
395
399RunRangeList::RunRangeList() : m_rangeList() {}
400
406void RunRangeList::addRun(const unsigned int run) {
407 // If the run is inside one of the ranges, do nothing.
408 if (std::binary_search(m_rangeList.begin(), m_rangeList.end(), run, RangeContainsRun()))
409 return;
410
411 // Else create a new range, containing a single run, and add it to the list.
412 m_rangeList.emplace(run, run);
413
414 // Now merge any ranges that are adjacent.
415 m_rangeList = std::accumulate(m_rangeList.begin(), m_rangeList.end(),
416 std::set<std::pair<unsigned int, unsigned int>>(), mergeAdjacentRanges);
417}
418
425void RunRangeList::addRunRange(unsigned int from, unsigned int to) {
426 for (; from <= to; ++from)
427 addRun(from);
428}
429
435void RunRangeList::addRunRange(const std::pair<unsigned int, unsigned int> &range) {
436 addRunRange(range.first, range.second);
437}
438
440// Anonymous helper functions.
442
443namespace // anonymous
444{
452void parseToken(std::vector<std::vector<unsigned int>> &parsedRuns, const std::string &token) {
453 std::vector<std::vector<unsigned int>> runs;
454 // Tokenise further on plus.
455 std::vector<std::string> subTokens;
456 boost::split(subTokens, token, boost::is_any_of("+"));
457 std::vector<unsigned int> runsToAdd;
458 for (auto const &subToken : subTokens) {
459 // E.g. "2012".
460 if (matchesFully(subToken, Regexs::SINGLE)) {
461 runsToAdd.emplace_back(std::stoi(subToken));
462 }
463 // E.g. "2012:2020".
464 else if (matchesFully(subToken, Regexs::RANGE)) {
465 // Fill in runs directly.
466 constexpr bool addRuns{false};
467 std::vector<std::string> rangeDetails;
468 rangeDetails.reserve(2);
469 boost::split(rangeDetails, subToken, boost::is_any_of(":"));
470 runs = generateRange(std::stoi(rangeDetails.front()), std::stoi(rangeDetails.back()), 1, addRuns);
471 }
472 // E.g. "2012:2020:4".
473 else if (matchesFully(subToken, Regexs::STEP_RANGE)) {
474 // Fill in runs directly.
475 constexpr bool addRuns{false};
476 std::vector<std::string> rangeDetails;
477 rangeDetails.reserve(3);
478 boost::split(rangeDetails, subToken, boost::is_any_of(":"));
479 runs = generateRange(std::stoi(rangeDetails[0]), std::stoi(rangeDetails[1]), std::stoi(rangeDetails[2]), addRuns);
480 }
481 // E.g. "2012-2020".
482 else if (matchesFully(subToken, Regexs::ADD_RANGE)) {
483 constexpr bool addRuns{true};
484 std::vector<std::string> rangeDetails;
485 rangeDetails.reserve(2);
486 boost::split(rangeDetails, subToken, boost::is_any_of("-"));
487 const auto generated = generateRange(std::stoi(rangeDetails.front()), std::stoi(rangeDetails.back()), 1, addRuns);
488 std::copy(generated.front().cbegin(), generated.front().cend(), back_inserter(runsToAdd));
489 }
490 // E.g. "2012-2020:4".
491 else if (matchesFully(subToken, Regexs::ADD_STEP_RANGE)) {
492 constexpr bool addRuns{true};
493 std::vector<std::string> rangeDetails;
494 rangeDetails.reserve(3);
495 boost::split(rangeDetails, subToken, boost::is_any_of("-:"));
496 const auto generated =
497 generateRange(std::stoi(rangeDetails[0]), std::stoi(rangeDetails[1]), std::stoi(rangeDetails[2]), addRuns);
498 std::copy(generated.front().cbegin(), generated.front().cend(), back_inserter(runsToAdd));
499 } else {
500 // We should never reach here - the validation done on the token
501 // previously should prevent any other possible scenario.
502 assert(false);
503 }
504 }
505 if (!runsToAdd.empty()) {
506 if (!runs.empty()) {
507 // We have either add ranges or step ranges. Never both.
508 throw std::runtime_error("Unable to handle a mixture of add ranges and step ranges");
509 }
510 runs.emplace_back(runsToAdd);
511 }
512 // Add the runs on to the end of parsedRuns, and return it.
513 std::copy(runs.begin(), runs.end(), std::back_inserter(parsedRuns));
514}
515
531std::vector<std::vector<unsigned int>> generateRange(unsigned int const from, unsigned int const to,
532 unsigned int const stepSize, bool const addRuns) {
533 if (stepSize == 0)
534 throw std::runtime_error("Unable to generate a range with a step size of zero.");
535
536 size_t limit;
537 auto limitStr = ConfigService::Instance().getValue<std::string>("loading.multifilelimit");
538 if (!limitStr.is_initialized() || !Strings::convert(limitStr.get(), limit)) {
539 limit = ConfigService::Instance().getFacility().multiFileLimit();
540 }
541
542 unsigned int const orderedTo = from > to ? from : to;
543 unsigned int const orderedFrom = from > to ? to : from;
544 unsigned int const numberOfFiles = (orderedTo - orderedFrom) / stepSize;
545 if (numberOfFiles > limit) {
546 std::stringstream sstream;
547 sstream << "The range from " << orderedFrom << " to " << orderedTo << " with step " << stepSize
548 << " would generate " << numberOfFiles << " files. "
549 << "This is greater than the current limit of " << limit << ". "
550 << "This limit can be configured in the Mantid.user.properties "
551 "file using the key loading.multifilelimit=200.";
552 throw std::range_error(sstream.str());
553 }
554
555 unsigned int currentRun = from;
556 std::vector<std::vector<unsigned int>> runs;
557
558 // If ascending range
559 if (from <= to) {
560 while (currentRun <= to) {
561 if (addRuns) {
562 if (runs.empty())
563 runs.emplace_back(1, currentRun);
564 else
565 runs.front().emplace_back(currentRun);
566 } else {
567 runs.emplace_back(1, currentRun);
568 }
569
570 currentRun += stepSize;
571 }
572 }
573 // Else descending range
574 else {
575 while (currentRun >= to) {
576 if (addRuns) {
577 if (runs.empty())
578 runs.emplace_back(1, currentRun);
579 else
580 runs.front().emplace_back(currentRun);
581 } else {
582 runs.emplace_back(1, currentRun);
583 }
584
585 // Guard against case where stepSize would take us into negative
586 // numbers (not supported by unsigned ints ...).
587 if (static_cast<int>(currentRun) - static_cast<int>(stepSize) < 0)
588 break;
589
590 currentRun -= stepSize;
591 }
592 }
593
594 return runs;
595}
596
604void validateToken(const std::string &token) {
605 // Each token must be non-empty.
606 if (token.empty())
607 throw std::runtime_error("A comma-separated token is empty.");
608
609 // Each token must begin and end with a numeric character.
610 if (!matchesFully(token, "[0-9].+[0-9]|[0-9]"))
611 throw std::runtime_error("The token \"" + token +
612 "\" is of an incorrect form. Does it begin or "
613 "end with a plus, minus or colon?");
614
615 // Each token must be one of the acceptable forms, i.e. a single run, an added
616 // range of runs, etc.
617 if (!matchesFully(token, Regexs::ANY))
618 throw std::runtime_error("The token \"" + token + "\" is of an incorrect form.");
619}
620
630bool matchesFully(const std::string &stringToMatch, const std::string &regexString, const bool caseless) {
631 boost::regex regex;
632
633 if (caseless)
634 regex = boost::regex("^(" + regexString + "$)", boost::regex::icase);
635 else
636 regex = boost::regex("^(" + regexString + "$)");
637
638 return boost::regex_match(stringToMatch, regex);
639}
640
650std::string getMatchingString(const std::string &regexString, const std::string &toParse, const bool caseless) {
651 boost::regex regex;
652 if (caseless) {
653 regex = boost::regex(regexString, boost::regex::icase);
654 } else {
655 regex = boost::regex(regexString);
656 }
657
658 boost::sregex_iterator it(toParse.begin(), toParse.end(), regex);
659
660 if (it == boost::sregex_iterator())
661 return "";
662
663 return it->str();
664}
665
675std::string pad(const unsigned int run, const std::string &instString) {
676 InstrumentInfo const instInfo = ConfigService::Instance().getInstrument(instString);
677 std::string prefix;
678 if (!instInfo.facility().noFilePrefix())
679 prefix = instInfo.filePrefix(run) + instInfo.delimiter();
680 unsigned int padLength = instInfo.zeroPadding(run);
681 std::string runStr = std::to_string(run);
682 if (runStr.size() < padLength)
683 runStr.insert(0, padLength - runStr.size(), '0');
684 else if (padLength > 0 && runStr.size() > padLength)
685 throw std::runtime_error("Could not parse run number \"" + runStr +
686 "\" since the instrument run number length required is " + std::to_string(padLength));
687 runStr.insert(0, prefix);
688 return runStr;
689}
690
698bool RangeContainsRun::operator()(const std::pair<unsigned int, unsigned int> &range, const unsigned int run) {
699 return range.second < run;
700}
701bool RangeContainsRun::operator()(const unsigned int run, const std::pair<unsigned int, unsigned int> &range) {
702 return run < range.first;
703}
704
714std::set<std::pair<unsigned int, unsigned int>> &
715mergeAdjacentRanges(std::set<std::pair<unsigned int, unsigned int>> &ranges,
716 const std::pair<unsigned int, unsigned int> &range) {
717 // If ranges is empty, just insert the new range.
718 if (ranges.empty()) {
719 ranges.insert(range);
720 }
721 // Else there are already some ranges present ...
722 else {
723 // ... if the last one is adjacent to the new range, merge the two.
724 if (ranges.rbegin()->second + 1 == range.first) {
725 unsigned int from = ranges.rbegin()->first;
726 unsigned int to = range.second;
727 std::pair<unsigned int, unsigned int> temp(from, to);
728
729 ranges.erase(--ranges.end(), ranges.end());
730 ranges.insert(temp);
731 }
732 // ... else just insert it.
733 else {
734 ranges.insert(range);
735 }
736 }
737 return ranges;
738}
739
750std::string &accumulateString(std::string &output, std::pair<unsigned int, unsigned int> runRange) {
751 if (!output.empty())
752 output += "_and_";
753
754 if (runRange.first == runRange.second)
755 output += std::to_string(runRange.first);
756 else
757 output += std::to_string(runRange.first) + "_to_" + std::to_string(runRange.second);
758
759 return output;
760}
761
769std::string toString(const RunRangeList &runRangeList) {
770 std::set<std::pair<unsigned int, unsigned int>> runRanges = runRangeList.rangeList();
771
772 // For each run range (pair of unsigned ints), call accumulateString and
773 // return the accumulated result.
774 return std::accumulate(runRanges.begin(), runRanges.end(), std::string(), accumulateString);
775}
776
777} // anonymous namespace
778
779} // namespace Mantid::Kernel::MultiFileNameParsing
The ConfigService class provides a simple facade to access the Configuration functionality of the Man...
Definition: ConfigService.h:63
const std::vector< FacilityInfo * > getFacilities() const
Get the list of facilities.
A functor that generates a vector of file names from the given vector of runs, and other state passed...
std::string m_instString
String that identifies the instrument.
std::string m_prefix
String that prefixes any generated file names.
GenerateFileName(std::string prefix, std::string suffix, std::string instString)
Constructor.
std::vector< std::string > operator()(const std::vector< unsigned int > &runs)
Overloaded function operator that generates a vector of file names from a vector of runs.
std::string m_suffix
String that suffixes any generated file names.
This class takes a string representing multiple files and parses it into a vector of vectors of file ...
std::string runString() const
Return the parsed run string.
std::vector< std::vector< unsigned int > > runs() const
Return the vector of vectors of parsed file names.
std::string underscoreString() const
Return the parsed underscore string.
std::string m_multiFileName
The given string to parse.
std::vector< std::vector< std::string > > m_fileNames
A vector of vectors of the parsed file names.
std::vector< std::vector< unsigned int > > parseMultiRunString(std::string runString)
Parses a string consisting of only run number info, into a vector of vector of run numbers.
std::string instString() const
Return the parsed instrument string.
std::vector< std::vector< unsigned int > > m_runs
A vector of vectors of the parsed runs.
void split()
Split the string to parse into its component parts.
std::set< std::string, ReverseCaselessCompare > m_validInstNames
All the valid instrument names.
bool trimWhiteSpaces() const
Return the setting for trimming whitespaces in run string.
bool m_trimWhiteSpaces
Flag to determine if string input should be trimmed of whitespace.
void setTrimWhiteSpaces(const bool &setting)
Set the flag for trimming whitespaces in run string.
std::string m_dirString
The various sections of the given string to parse.
void parse(const std::string &multiFileName)
Parse the given multiFileNameString.
bool operator()(const std::string &a, const std::string &b) const
Comparator for the set that holds instrument names in Parser.
A class that holds a list of ranges of runs.
std::set< std::pair< unsigned int, unsigned int > > m_rangeList
A set of pairs of unsigned ints, where each pair represents a range of runs.
void addRunRange(const unsigned int from, const unsigned int to)
Add a range of runs.
void addRun(const unsigned int run)
Add a run to the list of run ranges.
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
MANTID_KERNEL_DLL std::string suggestWorkspaceName(const std::vector< std::string > &fileNames)
Suggests a workspace name, given a vector of file names.
int convert(const std::string &A, T &out)
Convert a string into a number.
Definition: Strings.cpp:665
std::string toString(const T &value)
Convert a number to a string.
Definition: Strings.cpp:703
MortonT pad(IntT)
Pad an integer with a given number of padding bits.
STL namespace.
std::string to_string(const wide_integer< Bits, Signed > &n)