eminem
Parse Matrix Market files in C++
Loading...
Searching...
No Matches
Parser.hpp
Go to the documentation of this file.
1#ifndef EMINEM_PARSER_HPP
2#define EMINEM_PARSER_HPP
3
4#include <vector>
5#include <string>
6#include <cstring>
7#include <complex>
8#include <type_traits>
9#include <memory>
10#include <thread>
11#include <mutex>
12#include <condition_variable>
13
15#include "byteme/PerByte.hpp"
16
17#include "utils.hpp"
18
25namespace eminem {
26
31typedef unsigned long long Index;
32
40 int num_threads = 1;
41
47 std::size_t block_size = 65536;
48};
49
53template<typename Workspace_>
54class ThreadPool {
55public:
56 template<typename RunJob_>
57 ThreadPool(RunJob_ run_job, int num_threads) : my_helpers(num_threads) {
58 std::mutex init_mut;
59 std::condition_variable init_cv;
60 int num_initialized = 0;
61
62 my_threads.reserve(num_threads);
63 for (int t = 0; t < num_threads; ++t) {
64 // Copy lambda as it will be gone once this constructor finishes.
65 my_threads.emplace_back([run_job,this,&init_mut,&init_cv,&num_initialized](int thread) -> void {
66 Helper env; // allocating this locally within each thread to reduce the risk of false sharing.
67 my_helpers[thread] = &env;
68 {
69 std::lock_guard lck(init_mut);
70 ++num_initialized;
71 init_cv.notify_one();
72 }
73
74 while (1) {
75 std::unique_lock lck(env.mut);
76 env.cv.wait(lck, [&]() -> bool { return env.input_ready; });
77 if (env.terminated) {
78 return;
79 }
80 env.input_ready = false;
81
82 try {
83 run_job(env.work);
84 } catch (...) {
85 std::lock_guard elck(my_error_mut);
86 if (!my_error) {
87 my_error = std::current_exception();
88 }
89 }
90
91 env.has_output = true;
92 env.available = true;
93 env.cv.notify_one();
94 }
95 }, t);
96 }
97
98 // Only returning once all threads (and their specific mutexes) are initialized.
99 {
100 std::unique_lock ilck(init_mut);
101 init_cv.wait(ilck, [&]() -> bool { return num_initialized == num_threads; });
102 }
103 }
104
105 ~ThreadPool() {
106 for (auto envptr : my_helpers) {
107 auto& env = *envptr;
108 {
109 std::lock_guard lck(env.mut);
110 env.terminated = true;
111 env.input_ready = true;
112 }
113 env.cv.notify_one();
114 }
115 for (auto& thread : my_threads) {
116 thread.join();
117 }
118 }
119
120private:
121 std::vector<std::thread> my_threads;
122
123 struct Helper {
124 std::mutex mut;
125 std::condition_variable cv;
126 bool input_ready = false;
127 bool available = true;
128 bool has_output = false;
129 bool terminated = false;
130 Workspace_ work;
131 };
132 std::vector<Helper*> my_helpers;
133
134 std::mutex my_error_mut;
135 std::exception_ptr my_error;
136
137public:
138 template<typename CreateJob_, typename MergeJob_>
139 bool run(CreateJob_ create_job, MergeJob_ merge_job) {
140 auto num_threads = my_threads.size();
141 bool finished = false;
142 decltype(num_threads) thread = 0, finished_count = 0;
143
144 // We submit jobs by cycling through all threads, then we merge their results in order of submission.
145 // This is a less efficient worksharing scheme but it guarantees the same order of merges.
146 while (1) {
147 auto& env = *(my_helpers[thread]);
148 std::unique_lock lck(env.mut);
149 env.cv.wait(lck, [&]() -> bool { return env.available; });
150
151 {
152 std::lock_guard elck(my_error_mut);
153 if (my_error) {
154 std::rethrow_exception(my_error);
155 }
156 }
157 env.available = false;
158
159 if (env.has_output) {
160 // If the user requests an early quit from the merge job,
161 // there's no point processing the later merge jobs from
162 // other threads, so we just break out at this point.
163 if (!merge_job(env.work)) {
164 return false;
165 }
166 env.has_output = false;
167 }
168
169 if (finished) {
170 // Go through all threads one last time, making sure all results are merged.
171 ++finished_count;
172 if (finished_count == num_threads) {
173 break;
174 }
175 } else {
176 finished = !create_job(env.work);
177 env.input_ready = true;
178 lck.unlock();
179 env.cv.notify_one();
180 }
181
182 ++thread;
183 if (thread == num_threads) {
184 thread = 0;
185 }
186 }
187
188 return true;
189 }
190};
191
192template<class Input_>
193bool fill_to_next_newline(Input_& input, std::vector<char>& buffer, std::size_t block_size) {
194 buffer.resize(block_size);
195 auto done = input.extract(block_size, buffer.data());
196 buffer.resize(done.first);
197 if (!done.second || buffer.empty()) {
198 return false;
199 }
200 char last = buffer.back();
201 while (last != '\n') {
202 last = input.get();
203 buffer.push_back(last);
204 if (!input.advance()) {
205 return false;
206 }
207 }
208 return true;
209}
210
211inline std::size_t count_newlines(const std::vector<char>& buffer) {
212 std::size_t n = 0;
213 for (auto x : buffer) {
214 n += (x == '\n');
215 }
216 return n;
217}
222/* GENERAL COMMENTS:
223 * - This sticks to the specification described at https://math.nist.gov/MatrixMarket/reports/MMformat.ps.gz
224 * - We will allow both tabs and whitespaces when considering a 'blank' character.
225 * - We must consider the possibility that the final line of the file is not newline terminated.
226 */
227
233template<class Input_>
234class Parser {
235public:
240 Parser(std::unique_ptr<Input_> input, const ParserOptions& options) :
241 my_input(std::move(input)),
242 my_nthreads(options.num_threads),
243 my_block_size(options.block_size)
244 {}
245
246private:
247 std::unique_ptr<Input_> my_input;
248 int my_nthreads;
249 std::size_t my_block_size;
250
251 Index my_current_line = 0;
252 MatrixDetails my_details;
253
254 template<typename Input2_>
255 static bool chomp(Input2_& input) {
256 while (1) {
257 char x = input.get();
258 if (x != ' ' && x != '\t') {
259 return true;
260 }
261 if (!(input.advance())) {
262 break;
263 }
264 }
265 return false;
266 }
267
268 template<typename Input2_>
269 static bool advance_and_chomp(Input2_& input) {
270 // When the input is currently on a whitespace, we advance first so we
271 // avoid a redundant iteration where the comparison is always true.
272 if (!(input.advance())) {
273 return false;
274 }
275 return chomp(input);
276 }
277
278 template<typename Input2_>
279 static bool skip_lines(Input2_& input, Index& current_line) {
280 // Skip comments and empty lines.
281 while (1) {
282 char x = input.get();
283 if (x == '%') {
284 do {
285 if (!(input.advance())) {
286 return false;
287 }
288 } while (input.get() != '\n');
289 } else if (x != '\n') {
290 break;
291 }
292
293 if (!input.advance()) { // move past the newline.
294 return false;
295 }
296 ++current_line;
297 }
298 return true;
299 }
300
301private:
302 bool my_passed_banner = false;
303
304 struct ExpectedMatch {
305 ExpectedMatch(bool found, bool newline, bool remaining) : found(found), newline(newline), remaining(remaining) {}
306 ExpectedMatch() : ExpectedMatch(false, false, false) {}
307 bool found;
308 bool newline;
309 bool remaining;
310 };
311
312 ExpectedMatch advance_past_expected_string() {
313 if (!(my_input->advance())) { // move off the last character.
314 return ExpectedMatch(true, false, false);
315 }
316
317 char next = my_input->get();
318 if (next == ' ' || next == '\t') {
319 if (!advance_and_chomp(*my_input)) { // gobble up all of the remaining horizontal space.
320 return ExpectedMatch(true, false, false);
321 }
322 if (my_input->get() == '\n') {
323 bool remaining = my_input->advance(); // move past the newline for consistency with other functions.
324 return ExpectedMatch(true, true, remaining); // move past the newline for consistency with other functions.
325 }
326 return ExpectedMatch(true, false, true);
327
328 } else if (next == '\n') {
329 bool remaining = my_input->advance(); // move past the newline for consistency with other functions.
330 return ExpectedMatch(true, true, remaining);
331 }
332
333 // If the next character is not a space or whitespace, it's not a match.
334 return ExpectedMatch(false, true, true);
335 }
336
337 ExpectedMatch is_expected_string(const char* ptr, std::size_t len, std::size_t start) {
338 // It is assumed that the first 'start' characters of 'ptr' where
339 // already checked and matched before entering this function, and that
340 // 'my_input' is currently positioned at the start-th character, i.e.,
341 // 'ptr[start-1]' (and thus requires an advance() call before we can
342 // compare against 'ptr[start]').
343 for (std::size_t i = start; i < len; ++i) {
344 if (!my_input->advance()) {
345 return ExpectedMatch(false, false, false);
346 }
347 if (my_input->get() != ptr[i]) {
348 return ExpectedMatch(false, false, true);
349 }
350 }
351 return advance_past_expected_string();
352 }
353
354 ExpectedMatch is_expected_string(const char* ptr, std::size_t len) {
355 // Using a default start of 1, assuming that we've already compared
356 // the first character before entering this function.
357 return is_expected_string(ptr, len, 1);
358 }
359
360 bool parse_banner_object() {
361 ExpectedMatch res;
362
363 char x = my_input->get();
364 if (x == 'm') {
365 res = is_expected_string("matrix", 6);
366 my_details.object = Object::MATRIX;
367 } else if (x == 'v') {
368 res = is_expected_string("vector", 6);
369 my_details.object = Object::VECTOR;
370 }
371
372 if (!res.found) {
373 throw std::runtime_error("first banner field should be one of 'matrix' or 'vector'");
374 }
375 if (!res.remaining) {
376 throw std::runtime_error("end of file reached after the first banner field");
377 }
378
379 return res.newline;
380 }
381
382 bool parse_banner_format() {
383 ExpectedMatch res;
384
385 char x = my_input->get();
386 if (x == 'c') {
387 res = is_expected_string("coordinate", 10);
388 my_details.format = Format::COORDINATE;
389 } else if (x == 'a') {
390 res = is_expected_string("array", 5);
391 my_details.format = Format::ARRAY;
392 }
393
394 if (!res.found) {
395 throw std::runtime_error("second banner field should be one of 'coordinate' or 'array'");
396 }
397 if (!res.remaining) {
398 throw std::runtime_error("end of file reached after the second banner field");
399 }
400
401 return res.newline;
402 }
403
404 bool parse_banner_field() {
405 ExpectedMatch res;
406
407 char x = my_input->get();
408 if (x == 'i') {
409 res = is_expected_string("integer", 7);
410 my_details.field = Field::INTEGER;
411 } else if (x == 'd') {
412 res = is_expected_string("double", 6);
413 my_details.field = Field::DOUBLE;
414 } else if (x == 'c') {
415 res = is_expected_string("complex", 7);
416 my_details.field = Field::COMPLEX;
417 } else if (x == 'p') {
418 res = is_expected_string("pattern", 7);
419 my_details.field = Field::PATTERN;
420 } else if (x == 'r') {
421 res = is_expected_string("real", 4);
422 my_details.field = Field::REAL;
423 }
424
425 if (!res.found) {
426 throw std::runtime_error("third banner field should be one of 'real', 'integer', 'double', 'complex' or 'pattern'");
427 }
428 if (!res.remaining) {
429 throw std::runtime_error("end of file reached after the third banner field");
430 }
431
432 return res.newline;
433 }
434
435 bool parse_banner_symmetry() {
436 ExpectedMatch res;
437
438 char x = my_input->get();
439 if (x == 'g') {
440 res = is_expected_string("general", 7);
441 my_details.symmetry = Symmetry::GENERAL;
442 } else if (x == 'h') {
443 res = is_expected_string("hermitian", 9);
444 my_details.symmetry = Symmetry::HERMITIAN;
445 } else if (x == 's') {
446 if (my_input->advance()) {
447 char x = my_input->get();
448 if (x == 'k') {
449 res = is_expected_string("skew-symmetric", 14, 2);
450 my_details.symmetry = Symmetry::SKEW_SYMMETRIC;
451 } else {
452 res = is_expected_string("symmetric", 9, 2);
453 my_details.symmetry = Symmetry::SYMMETRIC;
454 }
455 }
456 }
457
458 if (!res.found) {
459 throw std::runtime_error("fourth banner field should be one of 'general', 'hermitian', 'skew-symmetric' or 'symmetric'");
460 }
461 if (!res.remaining) {
462 throw std::runtime_error("end of file reached after the fourth banner field");
463 }
464
465 return res.newline;
466 }
467
468 void scan_banner() {
469 if (my_passed_banner) {
470 throw std::runtime_error("banner has already been scanned");
471 }
472 if (!(my_input->valid())) {
473 throw std::runtime_error("failed to find banner line before end of file");
474 }
475 if (my_input->get() != '%') {
476 throw std::runtime_error("first line of the file should be the banner");
477 }
478
479 auto found_banner = is_expected_string("%%MatrixMarket", 14);
480 if (!found_banner.remaining) {
481 throw std::runtime_error("end of file reached before matching the banner");
482 }
483 if (!found_banner.found) {
484 throw std::runtime_error("first line of the file should be the banner");
485 }
486 if (found_banner.newline) {
487 throw std::runtime_error("end of line reached before matching the banner");
488 }
489
490 if (parse_banner_object()) {
491 throw std::runtime_error("end of line reached after the first banner field");
492 }
493 if (parse_banner_format()) {
494 throw std::runtime_error("end of line reached after the second banner field");
495 }
496
497 bool eol = false;
498 if (my_details.object == Object::MATRIX) {
499 if (parse_banner_field()) {
500 throw std::runtime_error("end of line reached after the third banner field");
501 }
502 eol = parse_banner_symmetry();
503 } else {
504 // The NIST spec doesn't say anything about symmetry for vector,
505 // and it doesn't really make sense anyway. We'll just set it to
506 // general and hope for the best.
507 my_details.symmetry = Symmetry::GENERAL;
508
509 // No need to throw on newline because this might be the last field AFAICT.
510 eol = parse_banner_field();
511 }
512
513 my_passed_banner = true;
514
515 // Ignoring all other fields until the newline. We can use a do/while
516 // to skip a comparison because we know that the current character
517 // cannot be a newline if eol = false.
518 if (!eol) {
519 do {
520 if (!(my_input->advance())) {
521 throw std::runtime_error("end of file reached before the end of the banner line");
522 }
523 } while (my_input->get() != '\n');
524 }
525
526 ++my_current_line;
527 return;
528 }
529
530public:
537 const MatrixDetails& get_banner() const {
538 if (!my_passed_banner) {
539 throw std::runtime_error("banner has not yet been scanned");
540 }
541 return my_details;
542 }
543
544private:
545 // Only calls with 'last_ = true' need to know if there are any remaining bytes after the newline.
546 // This is because all non-last calls with no remaining bytes must have thrown.
547 struct NotLastSizeInfo {
548 Index index = 0;
549 };
550
551 struct LastSizeInfo {
552 Index index = 0;
553 bool remaining = false;
554 };
555
556 template<bool last_>
557 using SizeInfo = typename std::conditional<last_, LastSizeInfo, NotLastSizeInfo>::type;
558
559 template<bool last_, class Input2_>
560 static SizeInfo<last_> scan_integer_field(bool size, Input2_& input, Index overall_line_count) {
561 SizeInfo<last_> output;
562 bool found = false;
563
564 auto what = [&]() -> std::string {
565 if (size) {
566 return "size";
567 } else {
568 return "index";
569 }
570 };
571
572 while (1) {
573 char x = input.get();
574 switch(x) {
575 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
576 found = true;
577 output.index *= 10;
578 output.index += x - '0';
579 break;
580 case '\n':
581 // This check only needs to be put here, as all blanks should be chomped before calling
582 // this function; so we must start on a non-blank character. This starting character is either:
583 // - a digit, in which case found = true and this check is unnecessary.
584 // - a non-newline non-digit, in case we throw.
585 // - a newline, in which case we arrive here.
586 if (!found) {
587 throw std::runtime_error("empty " + what() + " field on line " + std::to_string(overall_line_count + 1));
588 }
589 if constexpr(last_) {
590 output.remaining = input.advance(); // advance past the newline.
591 return output;
592 }
593 throw std::runtime_error("unexpected newline when parsing " + what() + " field on line " + std::to_string(overall_line_count + 1));
594 case ' ': case '\t':
595 if (!advance_and_chomp(input)) { // skipping the current and subsequent blanks.
596 if constexpr(last_) {
597 return output;
598 } else {
599 throw std::runtime_error("unexpected end of file when parsing " + what() + " field on line " + std::to_string(overall_line_count + 1));
600 }
601 }
602 if constexpr(last_) {
603 if (input.get() != '\n') {
604 throw std::runtime_error("expected newline after the last " + what() + " field on line " + std::to_string(overall_line_count + 1));
605 }
606 output.remaining = input.advance(); // advance past the newline.
607 }
608 return output;
609 default:
610 throw std::runtime_error("unexpected character when parsing " + what() + " field on line " + std::to_string(overall_line_count + 1));
611 }
612
613 if (!(input.advance())) { // moving past the current digit.
614 if constexpr(last_) {
615 break;
616 } else {
617 throw std::runtime_error("unexpected end of file when parsing " + what() + " field on line " + std::to_string(overall_line_count + 1));
618 }
619 }
620 }
621
622 return output;
623 }
624
625 template<bool last_, class Input2_>
626 static SizeInfo<last_> scan_size_field(Input2_& input, Index overall_line_count) {
627 return scan_integer_field<last_>(true, input, overall_line_count);
628 }
629
630 template<bool last_, class Input2_>
631 static SizeInfo<last_> scan_index_field(Input2_& input, Index overall_line_count) {
632 return scan_integer_field<last_>(false, input, overall_line_count);
633 }
634
635private:
636 bool my_passed_size = false;
637 Index my_nrows = 0, my_ncols = 0, my_nlines = 0;
638
639 void scan_size() {
640 if (!(my_input->valid())) {
641 throw std::runtime_error("failed to find size line before end of file");
642 }
643
644 // Handling stray comments, empty lines, and leading whitespace.
645 if (!skip_lines(*my_input, my_current_line)) {
646 throw std::runtime_error("failed to find size line before end of file");
647 }
648 if (!chomp(*my_input)) {
649 throw std::runtime_error("expected at least one size field on line " + std::to_string(my_current_line + 1));
650 }
651
652 if (my_details.object == Object::MATRIX) {
653 if (my_details.format == Format::COORDINATE) {
654 auto first_field = scan_size_field<false>(*my_input, my_current_line);
655 my_nrows = first_field.index;
656
657 auto second_field = scan_size_field<false>(*my_input, my_current_line);
658 my_ncols = second_field.index;
659
660 auto third_field = scan_size_field<true>(*my_input, my_current_line);
661 my_nlines = third_field.index;
662
663 } else { // i.e., my_details.format == Format::ARRAY
664 auto first_field = scan_size_field<false>(*my_input, my_current_line);
665 my_nrows = first_field.index;
666
667 auto second_field = scan_size_field<true>(*my_input, my_current_line);
668 my_ncols = second_field.index;
669 my_nlines = my_nrows * my_ncols;
670 }
671
672 } else {
673 if (my_details.format == Format::COORDINATE) {
674 auto first_field = scan_size_field<false>(*my_input, my_current_line);
675 my_nrows = first_field.index;
676
677 auto second_field = scan_size_field<true>(*my_input, my_current_line);
678 my_nlines = second_field.index;
679
680 } else { // i.e., my_details.format == Format::ARRAY
681 auto first_field = scan_size_field<true>(*my_input, my_current_line);
682 my_nlines = first_field.index;
683 my_nrows = my_nlines;
684 }
685 my_ncols = 1;
686 }
687
688 my_passed_size = true;
689 }
690
691public:
699 Index get_nrows() const {
700 if (!my_passed_size) {
701 throw std::runtime_error("size line has not yet been scanned");
702 }
703 return my_nrows;
704 }
705
713 Index get_ncols() const {
714 if (!my_passed_size) {
715 throw std::runtime_error("size line has not yet been scanned");
716 }
717 return my_ncols;
718 }
719
728 if (!my_passed_size) {
729 throw std::runtime_error("size line has not yet been scanned");
730 }
731 return my_nlines;
732 }
733
734public:
740 scan_banner();
741 scan_size();
742 return;
743 }
744
745private:
746 template<typename Type_>
747 struct ParseInfo {
748 Type_ value;
749 bool remaining;
750 };
751
752 template<typename Workspace_>
753 bool configure_parallel_workspace(Workspace_& work) {
754 bool available = fill_to_next_newline(*my_input, work.buffer, my_block_size);
755 work.contents.clear();
756 work.overall_line = my_current_line;
757 my_current_line += count_newlines(work.buffer);
758 return available;
759 }
760
761 void check_num_lines_loop(Index data_line_count) const {
762 if (data_line_count >= my_nlines) {
763 throw std::runtime_error("more lines present than specified in the header (" + std::to_string(data_line_count) + " versus " + std::to_string(my_nlines) + ")");
764 }
765 }
766
767 void check_num_lines_final(bool finished, Index data_line_count) const {
768 if (finished) {
769 if (data_line_count != my_nlines) {
770 // Must be fewer, otherwise we would have triggered the error in check_num_lines_loop() during iteration.
771 throw std::runtime_error("fewer lines present than specified in the header (" + std::to_string(data_line_count) + " versus " + std::to_string(my_nlines) + ")");
772 }
773 }
774 }
775
776private:
777 void check_matrix_coordinate_line(Index currow, Index curcol, Index overall_line_count) const {
778 if (!currow) {
779 throw std::runtime_error("row index must be positive on line " + std::to_string(overall_line_count + 1));
780 }
781 if (currow > my_nrows) {
782 throw std::runtime_error("row index out of range on line " + std::to_string(overall_line_count + 1));
783 }
784 if (!curcol) {
785 throw std::runtime_error("column index must be positive on line " + std::to_string(overall_line_count + 1));
786 }
787 if (curcol > my_ncols) {
788 throw std::runtime_error("column index out of range on line " + std::to_string(overall_line_count + 1));
789 }
790 }
791
792 template<typename Type_, class Input2_, typename FieldParser_, class WrappedStore_>
793 bool scan_matrix_coordinate_non_pattern_base(Input2_& input, Index& overall_line_count, FieldParser_& fparser, WrappedStore_ wstore) const {
794 bool valid = input.valid();
795 while (valid) {
796 // Handling stray comments, empty lines, and leading spaces.
797 if (!skip_lines(input, overall_line_count)) {
798 break;
799 }
800 if (!chomp(input)) {
801 throw std::runtime_error("expected at least three fields for a coordinate matrix on line " + std::to_string(overall_line_count + 1));
802 }
803
804 auto first_field = scan_index_field<false>(input, overall_line_count);
805 auto second_field = scan_index_field<false>(input, overall_line_count);
806 check_matrix_coordinate_line(first_field.index, second_field.index, overall_line_count);
807
808 // 'fparser' should leave 'input' at the start of the next line, if any exists.
809 ParseInfo<Type_> res = fparser(input, overall_line_count);
810 if (!wstore(first_field.index, second_field.index, res.value)) {
811 return false;
812 }
813 ++overall_line_count;
814 valid = res.remaining;
815 }
816
817 return true;
818 }
819
820 template<typename Type_, class FieldParser_, class Store_>
821 bool scan_matrix_coordinate_non_pattern(Store_ store) {
822 bool finished = false;
823 Index current_data_line = 0;
824
825 if (my_nthreads == 1) {
826 FieldParser_ fparser;
827 finished = scan_matrix_coordinate_non_pattern_base<Type_>(
828 *my_input,
829 my_current_line,
830 fparser,
831 [&](Index r, Index c, Type_ value) -> bool {
832 check_num_lines_loop(current_data_line);
833 ++current_data_line;
834 return store(r, c, value);
835 }
836 );
837
838 } else {
839 struct Workspace {
840 std::vector<char> buffer;
841 FieldParser_ fparser;
842 std::vector<std::tuple<Index, Index, Type_> > contents;
843 Index overall_line;
844 };
845
846 ThreadPool<Workspace> tp(
847 [&](Workspace& work) -> bool {
848 byteme::RawBufferReader reader(reinterpret_cast<const unsigned char*>(work.buffer.data()), work.buffer.size());
850 return scan_matrix_coordinate_non_pattern_base<Type_>(
851 pb,
852 work.overall_line,
853 work.fparser,
854 [&](Index r, Index c, Type_ value) -> bool {
855 work.contents.emplace_back(r, c, value);
856 return true; // threads cannot quit early in their parallel sections; this (and thus scan_*_base) must always return true.
857 }
858 );
859 },
860 my_nthreads
861 );
862
863 finished = tp.run(
864 [&](Workspace& work) -> bool {
865 return configure_parallel_workspace(work);
866 },
867 [&](Workspace& work) -> bool {
868 for (const auto& con : work.contents) {
869 check_num_lines_loop(current_data_line); // defer check here for the correctly sync'd value of current_data_line.
870 if (!store(std::get<0>(con), std::get<1>(con), std::get<2>(con))) {
871 return false;
872 }
873 ++current_data_line;
874 }
875 return true;
876 }
877 );
878 }
879
880 check_num_lines_final(finished, current_data_line);
881 return finished;
882 }
883
884private:
885 template<class Input2_, class WrappedStore_>
886 bool scan_matrix_coordinate_pattern_base(Input2_& input, Index& overall_line_count, WrappedStore_ wstore) const {
887 bool valid = input.valid();
888 while (valid) {
889 // Handling stray comments, empty lines, and leading spaces.
890 if (!skip_lines(input, overall_line_count)) {
891 break;
892 }
893 if (!chomp(input)) {
894 throw std::runtime_error("expected two fields for a pattern matrix on line " + std::to_string(overall_line_count + 1));
895 }
896
897 auto first_field = scan_index_field<false>(input, overall_line_count);
898 auto second_field = scan_index_field<true>(input, overall_line_count);
899 check_matrix_coordinate_line(first_field.index, second_field.index, overall_line_count);
900
901 if (!wstore(first_field.index, second_field.index)) {
902 return false;
903 }
904 ++overall_line_count;
905 valid = second_field.remaining;
906 }
907
908 return true;
909 }
910
911 template<class Store_>
912 bool scan_matrix_coordinate_pattern(Store_ store) {
913 bool finished = false;
914 Index current_data_line = 0;
915
916 if (my_nthreads == 1) {
917 finished = scan_matrix_coordinate_pattern_base(
918 *my_input,
919 my_current_line,
920 [&](Index r, Index c) -> bool {
921 check_num_lines_loop(current_data_line);
922 ++current_data_line;
923 return store(r, c);
924 }
925 );
926
927 } else {
928 struct Workspace {
929 std::vector<char> buffer;
930 std::vector<std::tuple<Index, Index> > contents;
931 Index overall_line;
932 };
933
934 ThreadPool<Workspace> tp(
935 [&](Workspace& work) -> bool {
936 byteme::RawBufferReader reader(reinterpret_cast<const unsigned char*>(work.buffer.data()), work.buffer.size());
938 return scan_matrix_coordinate_pattern_base(
939 pb,
940 work.overall_line,
941 [&](Index r, Index c) -> bool {
942 work.contents.emplace_back(r, c);
943 return true; // threads cannot quit early in their parallel sections; this (and thus scan_*_base) must always return true.
944 }
945 );
946 },
947 my_nthreads
948 );
949
950 finished = tp.run(
951 [&](Workspace& work) -> bool {
952 return configure_parallel_workspace(work);
953 },
954 [&](Workspace& work) -> bool {
955 for (const auto& con : work.contents) {
956 check_num_lines_loop(current_data_line);
957 if (!store(std::get<0>(con), std::get<1>(con))) {
958 return false;
959 }
960 ++current_data_line;
961 }
962 return true;
963 }
964 );
965 }
966
967 check_num_lines_final(finished, current_data_line);
968 return finished;
969 }
970
971private:
972 void check_vector_coordinate_line(Index currow, Index overall_line_count) const {
973 if (!currow) {
974 throw std::runtime_error("row index must be positive on line " + std::to_string(overall_line_count + 1));
975 }
976 if (currow > my_nrows) {
977 throw std::runtime_error("row index out of range on line " + std::to_string(overall_line_count + 1));
978 }
979 }
980
981 template<typename Type_, class Input2_, class FieldParser_, class WrappedStore_>
982 bool scan_vector_coordinate_non_pattern_base(Input2_& input, Index& overall_line_count, FieldParser_& fparser, WrappedStore_ wstore) const {
983 bool valid = input.valid();
984 while (valid) {
985 // handling stray comments, empty lines, and leading spaces.
986 if (!skip_lines(input, overall_line_count)) {
987 break;
988 }
989 if (!chomp(input)) {
990 throw std::runtime_error("expected at least two fields for a coordinate vector on line " + std::to_string(overall_line_count + 1));
991 }
992
993 auto first_field = scan_index_field<false>(input, overall_line_count);
994 check_vector_coordinate_line(first_field.index, overall_line_count);
995
996 // 'fparser' should leave 'input' at the start of the next line, if any exists.
997 ParseInfo<Type_> res = fparser(input, overall_line_count);
998 if (!wstore(first_field.index, res.value)) {
999 return false;
1000 }
1001 ++overall_line_count;
1002 valid = res.remaining;
1003 }
1004
1005 return true;
1006 }
1007
1008 template<typename Type_, class FieldParser_, class Store_>
1009 bool scan_vector_coordinate_non_pattern(Store_ store) {
1010 bool finished = false;
1011 Index current_data_line = 0;
1012
1013 if (my_nthreads == 1) {
1014 FieldParser_ fparser;
1015 finished = scan_vector_coordinate_non_pattern_base<Type_>(
1016 *my_input,
1017 my_current_line,
1018 fparser,
1019 [&](Index r, Type_ value) -> bool {
1020 check_num_lines_loop(current_data_line);
1021 ++current_data_line;
1022 return store(r, 1, value);
1023 }
1024 );
1025
1026 } else {
1027 struct Workspace {
1028 std::vector<char> buffer;
1029 FieldParser_ fparser;
1030 std::vector<std::tuple<Index, Type_> > contents;
1031 Index overall_line;
1032 };
1033
1034 ThreadPool<Workspace> tp(
1035 [&](Workspace& work) -> bool {
1036 byteme::RawBufferReader reader(reinterpret_cast<const unsigned char*>(work.buffer.data()), work.buffer.size());
1038 return scan_vector_coordinate_non_pattern_base<Type_>(
1039 pb,
1040 work.overall_line,
1041 work.fparser,
1042 [&](Index r, Type_ value) -> bool {
1043 work.contents.emplace_back(r, value);
1044 return true; // threads cannot quit early in their parallel sections; this (and thus scan_*_base) must always return true.
1045 }
1046 );
1047 },
1048 my_nthreads
1049 );
1050
1051 finished = tp.run(
1052 [&](Workspace& work) -> bool {
1053 return configure_parallel_workspace(work);
1054 },
1055 [&](Workspace& work) -> bool {
1056 for (const auto& con : work.contents) {
1057 check_num_lines_loop(current_data_line);
1058 if (!store(std::get<0>(con), 1, std::get<1>(con))) {
1059 return false;
1060 }
1061 ++current_data_line;
1062 }
1063 return true;
1064 }
1065 );
1066 }
1067
1068 check_num_lines_final(finished, current_data_line);
1069 return finished;
1070 }
1071
1072private:
1073 template<class Input2_, class WrappedStore_>
1074 bool scan_vector_coordinate_pattern_base(Input2_& input, Index& overall_line_count, WrappedStore_ wstore) const {
1075 bool valid = input.valid();
1076 while (valid) {
1077 // Handling stray comments, empty lines, and leading spaces.
1078 if (!skip_lines(input, overall_line_count)) {
1079 break;
1080 }
1081 if (!chomp(input)) {
1082 throw std::runtime_error("expected one field for a coordinate vector on line " + std::to_string(overall_line_count + 1));
1083 }
1084
1085 auto first_field = scan_index_field<true>(input, overall_line_count);
1086 check_vector_coordinate_line(first_field.index, overall_line_count);
1087
1088 if (!wstore(first_field.index)) {
1089 return false;
1090 }
1091 ++overall_line_count;
1092 valid = first_field.remaining;
1093 }
1094
1095 return true;
1096 }
1097
1098 template<class Store_>
1099 bool scan_vector_coordinate_pattern(Store_ store) {
1100 bool finished = false;
1101 Index current_data_line = 0;
1102
1103 if (my_nthreads == 1) {
1104 finished = scan_vector_coordinate_pattern_base(
1105 *my_input,
1106 my_current_line,
1107 [&](Index r) -> bool {
1108 check_num_lines_loop(current_data_line);
1109 ++current_data_line;
1110 return store(r, 1);
1111 }
1112 );
1113
1114 } else {
1115 struct Workspace {
1116 std::vector<char> buffer;
1117 std::vector<Index> contents;
1118 Index overall_line;
1119 };
1120
1121 ThreadPool<Workspace> tp(
1122 [&](Workspace& work) -> bool {
1123 byteme::RawBufferReader reader(reinterpret_cast<const unsigned char*>(work.buffer.data()), work.buffer.size());
1125 return scan_vector_coordinate_pattern_base(
1126 pb,
1127 work.overall_line,
1128 [&](Index r) -> bool {
1129 work.contents.emplace_back(r);
1130 return true; // threads cannot quit early in their parallel sections; this (and thus scan_*_base) must always return true.
1131 }
1132 );
1133 },
1134 my_nthreads
1135 );
1136
1137 finished = tp.run(
1138 [&](Workspace& work) -> bool {
1139 return configure_parallel_workspace(work);
1140 },
1141 [&](Workspace& work) -> bool {
1142 for (const auto& r : work.contents) {
1143 check_num_lines_loop(current_data_line);
1144 if (!store(r, 1)) {
1145 return false;
1146 }
1147 ++current_data_line;
1148 }
1149 return true;
1150 }
1151 );
1152 }
1153
1154 check_num_lines_final(finished, current_data_line);
1155 return finished;
1156 }
1157
1158private:
1159 template<typename Type_, class Input2_, class FieldParser_, class WrappedStore_>
1160 bool scan_matrix_array_base(Input2_& input, Index& overall_line_count, FieldParser_& fparser, WrappedStore_ wstore) const {
1161 bool valid = input.valid();
1162 while (valid) {
1163 // Handling stray comments, empty lines, and leading spaces.
1164 if (!skip_lines(input, overall_line_count)) {
1165 break;
1166 }
1167 if (!chomp(input)) {
1168 throw std::runtime_error("expected at least one field for an array matrix on line " + std::to_string(overall_line_count + 1));
1169 }
1170
1171 // 'fparser' should leave 'input' at the start of the next line, if any exists.
1172 ParseInfo<Type_> res = fparser(input, overall_line_count);
1173 if (!wstore(res.value)) {
1174 return false;
1175 }
1176 ++overall_line_count;
1177 valid = res.remaining;
1178 }
1179
1180 return true;
1181 }
1182
1183 template<typename Type_, class FieldParser_, class Store_>
1184 bool scan_matrix_array(Store_ store) {
1185 bool finished = false;
1186 Index current_data_line = 0;
1187
1188 Index currow = 1, curcol = 1;
1189 auto increment = [&]() {
1190 ++currow;
1191 if (currow > my_nrows) {
1192 ++curcol;
1193 currow = 1;
1194 }
1195 };
1196
1197 if (my_nthreads == 1) {
1198 FieldParser_ fparser;
1199 finished = scan_matrix_array_base<Type_>(
1200 *my_input,
1201 my_current_line,
1202 fparser,
1203 [&](Type_ value) -> bool {
1204 check_num_lines_loop(current_data_line);
1205 if (!store(currow, curcol, value)) {
1206 return false;
1207 }
1208 ++current_data_line;
1209 increment();
1210 return true;
1211 }
1212 );
1213
1214 } else {
1215 struct Workspace {
1216 std::vector<char> buffer;
1217 FieldParser_ fparser;
1218 std::vector<Type_> contents;
1219 Index overall_line;
1220 };
1221
1222 ThreadPool<Workspace> tp(
1223 [&](Workspace& work) -> bool {
1224 byteme::RawBufferReader reader(reinterpret_cast<const unsigned char*>(work.buffer.data()), work.buffer.size());
1226 return scan_matrix_array_base<Type_>(
1227 pb,
1228 work.overall_line,
1229 work.fparser,
1230 [&](Type_ value) -> bool {
1231 work.contents.emplace_back(value);
1232 return true; // threads cannot quit early in their parallel sections; this (and thus scan_*_base) must always return true.
1233 }
1234 );
1235 },
1236 my_nthreads
1237 );
1238
1239 finished = tp.run(
1240 [&](Workspace& work) -> bool {
1241 return configure_parallel_workspace(work);
1242 },
1243 [&](Workspace& work) -> bool {
1244 for (const auto& val : work.contents) {
1245 check_num_lines_loop(current_data_line);
1246 if (!store(currow, curcol, val)) {
1247 return false;
1248 }
1249 ++current_data_line;
1250 increment();
1251 }
1252 return true;
1253 }
1254 );
1255 }
1256
1257 check_num_lines_final(finished, current_data_line);
1258 return finished;
1259 }
1260
1261private:
1262 template<typename Type_, class Input2_, class FieldParser_, class WrappedStore_>
1263 bool scan_vector_array_base(Input2_& input, Index& overall_line_count, FieldParser_& fparser, WrappedStore_ wstore) const {
1264 bool valid = input.valid();
1265 while (valid) {
1266 // Handling stray comments, empty lines, and leading spaces.
1267 if (!skip_lines(input, overall_line_count)) {
1268 break;
1269 }
1270 if (!chomp(input)) {
1271 throw std::runtime_error("expected at least one field for an array vector on line " + std::to_string(overall_line_count + 1));
1272 }
1273
1274 // 'fparser' should leave 'input' at the start of the next line, if any exists.
1275 ParseInfo<Type_> res = fparser(input, overall_line_count);
1276 if (!wstore(res.value)) {
1277 return false;
1278 }
1279 ++overall_line_count;
1280 valid = res.remaining;
1281 }
1282
1283 return true;
1284 }
1285
1286 template<typename Type_, class FieldParser_, class Store_>
1287 bool scan_vector_array(Store_ store) {
1288 bool finished = false;
1289 Index current_data_line = 0;
1290 if (my_nthreads == 1) {
1291 FieldParser_ fparser;
1292 finished = scan_vector_array_base<Type_>(
1293 *my_input,
1294 my_current_line,
1295 fparser,
1296 [&](Type_ value) -> bool {
1297 check_num_lines_loop(current_data_line);
1298 ++current_data_line;
1299 return store(current_data_line, 1, value);
1300 }
1301 );
1302
1303 } else {
1304 struct Workspace {
1305 std::vector<char> buffer;
1306 FieldParser_ fparser;
1307 std::vector<Type_> contents;
1308 Index overall_line;
1309 };
1310
1311 ThreadPool<Workspace> tp(
1312 [&](Workspace& work) -> bool {
1313 byteme::RawBufferReader reader(reinterpret_cast<const unsigned char*>(work.buffer.data()), work.buffer.size());
1315 return scan_vector_array_base<Type_>(
1316 pb,
1317 work.overall_line,
1318 work.fparser,
1319 [&](Type_ value) -> bool {
1320 work.contents.emplace_back(value);
1321 return true; // threads cannot quit early in their parallel sections; this (and thus scan_*_base) must always return true.
1322 }
1323 );
1324 },
1325 my_nthreads
1326 );
1327
1328 finished = tp.run(
1329 [&](Workspace& work) -> bool {
1330 return configure_parallel_workspace(work);
1331 },
1332 [&](Workspace& work) -> bool {
1333 for (const auto& val : work.contents) {
1334 check_num_lines_loop(current_data_line);
1335 ++current_data_line;
1336 if (!store(current_data_line, 1, val)) {
1337 return false;
1338 }
1339 }
1340 return true;
1341 }
1342 );
1343 }
1344
1345 check_num_lines_final(finished, current_data_line);
1346 return finished;
1347 }
1348
1349private:
1350 void check_preamble() const {
1351 if (!my_passed_banner || !my_passed_size) {
1352 throw std::runtime_error("banner or size lines have not yet been parsed");
1353 }
1354 }
1355
1356 template<typename Type_>
1357 class IntegerFieldParser {
1358 public:
1359 template<class Input2_>
1360 ParseInfo<Type_> operator()(Input2_& input, Index overall_line_count) {
1361 bool negative = (input.get() == '-');
1362 if (negative) {
1363 if (!(input.advance())) {
1364 throw std::runtime_error("premature termination of an integer on line " + std::to_string(overall_line_count + 1));
1365 }
1366 }
1367
1368 Type_ val = 0;
1369 auto finish = [&](bool valid) -> ParseInfo<Type_> {
1370 ParseInfo<Type_> output;
1371 output.remaining = valid;
1372 if (negative) {
1373 val *= -1;
1374 }
1375 output.value = val;
1376 return output;
1377 };
1378
1379 bool found = false;
1380 while (1) {
1381 char x = input.get();
1382 switch (x) {
1383 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
1384 val *= 10;
1385 val += x - '0';
1386 found = true;
1387 break;
1388 case ' ': case '\t':
1389 if (!advance_and_chomp(input)) { // skipping past the current position before chomping.
1390 return finish(false);
1391 }
1392 if (input.get() != '\n') {
1393 throw std::runtime_error("more fields than expected on line " + std::to_string(overall_line_count + 1));
1394 }
1395 return finish(input.advance()); // move past the newline.
1396 case '\n':
1397 // This check only needs to be put here, as all blanks should be chomped before calling
1398 // this function; so we must start on a non-blank character. This starting character is either:
1399 // - a digit, in which case found = true and this check is unnecessary.
1400 // - a non-newline non-digit, in case we throw.
1401 // - a newline, in which case we arrive here.
1402 if (!found) {
1403 throw std::runtime_error("empty integer field on line " + std::to_string(overall_line_count + 1));
1404 }
1405 return finish(input.advance()); // move past the newline.
1406 default:
1407 throw std::runtime_error("expected an integer value on line " + std::to_string(overall_line_count + 1));
1408 }
1409
1410 if (!(input.advance())) {
1411 break;
1412 }
1413 }
1414
1415 return finish(false);
1416 }
1417 };
1418
1419public:
1433 template<typename Type_ = int, class Store_>
1434 bool scan_integer(Store_ store) {
1435 check_preamble();
1436
1437 auto wrapped_store = [&](Index r, Index c, Type_ val) -> bool {
1438 if constexpr(std::is_same<typename std::invoke_result<Store_, Index, Index, Type_>::type, bool>::value) {
1439 return store(r, c, val);
1440 } else {
1441 store(r, c, val);
1442 return true;
1443 }
1444 };
1445
1446 if (my_details.format == Format::COORDINATE) {
1447 if (my_details.object == Object::MATRIX) {
1448 return scan_matrix_coordinate_non_pattern<Type_, IntegerFieldParser<Type_> >(std::move(wrapped_store));
1449 } else {
1450 return scan_vector_coordinate_non_pattern<Type_, IntegerFieldParser<Type_> >(std::move(wrapped_store));
1451 }
1452 } else {
1453 if (my_details.object == Object::MATRIX) {
1454 return scan_matrix_array<Type_, IntegerFieldParser<Type_> >(std::move(wrapped_store));
1455 } else {
1456 return scan_vector_array<Type_, IntegerFieldParser<Type_> >(std::move(wrapped_store));
1457 }
1458 }
1459 }
1460
1461private:
1462 template<typename Type_>
1463 static Type_ convert_to_real(const std::string& temporary, Index overall_line_count) {
1464 Type_ output;
1465 std::size_t n = 0;
1466
1467 try {
1468 if constexpr(std::is_same<Type_, float>::value) {
1469 output = std::stof(temporary, &n);
1470 } else if constexpr(std::is_same<Type_, long double>::value) {
1471 output = std::stold(temporary, &n);
1472 } else {
1473 output = std::stod(temporary, &n);
1474 }
1475 } catch (std::invalid_argument& e) {
1476 throw std::runtime_error("failed to convert value to a real number on line " + std::to_string(overall_line_count + 1));
1477 }
1478
1479 if (n != temporary.size()) {
1480 throw std::runtime_error("failed to convert value to a real number on line " + std::to_string(overall_line_count + 1));
1481 }
1482
1483 return output;
1484 }
1485
1486 template<typename Type_>
1487 class RealFieldParser {
1488 public:
1489 template<class Input2_>
1490 ParseInfo<Type_> operator()(Input2_& input, Index overall_line_count) {
1491 ParseInfo<Type_> output;
1492 output.remaining = true;
1493
1494 temporary.clear();
1495 char x = input.get();
1496 while (1) {
1497 if (x == '\n') {
1498 if (temporary.empty()) {
1499 // This is the only place we need to check as temporary is extended immediately after this.
1500 throw std::runtime_error("empty number field on line " + std::to_string(overall_line_count + 1));
1501 }
1502 output.remaining = input.advance(); // move past the newline.
1503 break;
1504 }
1505 temporary += x;
1506 if (!(input.advance())) {
1507 output.remaining = false;
1508 break;
1509 }
1510 x = input.get();
1511 // We shift the blank space check here, as 'store()' is always called after chomping leading blanks;
1512 // so there wouldn't be any point doing this check at the start of the first loop iteration.
1513 if (x == ' ' || x == '\t') {
1514 if (!advance_and_chomp(input)) { // skipping past the current position before chomping.
1515 output.remaining = false;
1516 break;
1517 }
1518 if (input.get() != '\n') {
1519 throw std::runtime_error("more fields than expected on line " + std::to_string(overall_line_count + 1));
1520 }
1521 output.remaining = input.advance(); // move past the newline.
1522 break;
1523 }
1524 }
1525
1526 output.value = convert_to_real<Type_>(temporary, overall_line_count);
1527 return output;
1528 }
1529
1530 private:
1531 std::string temporary;
1532 };
1533
1534public:
1548 template<typename Type_ = double, class Store_>
1549 bool scan_real(Store_&& store) {
1550 check_preamble();
1551
1552 auto store_real = [&](Index r, Index c, Type_ val) -> bool {
1553 if constexpr(std::is_same<typename std::invoke_result<Store_, Index, Index, Type_>::type, bool>::value) {
1554 return store(r, c, val);
1555 } else {
1556 store(r, c, val);
1557 return true;
1558 }
1559 };
1560
1561 if (my_details.format == Format::COORDINATE) {
1562 if (my_details.object == Object::MATRIX) {
1563 return scan_matrix_coordinate_non_pattern<Type_, RealFieldParser<Type_> >(std::move(store_real));
1564 } else {
1565 return scan_vector_coordinate_non_pattern<Type_, RealFieldParser<Type_> >(std::move(store_real));
1566 }
1567 } else {
1568 if (my_details.object == Object::MATRIX) {
1569 return scan_matrix_array<Type_, RealFieldParser<Type_> >(std::move(store_real));
1570 } else {
1571 return scan_vector_array<Type_, RealFieldParser<Type_> >(std::move(store_real));
1572 }
1573 }
1574 }
1575
1590 template<typename Type_ = double, class Store_>
1591 bool scan_double(Store_ store) {
1592 return scan_real<Type_, Store_>(std::move(store));
1593 }
1594
1595private:
1596 template<typename InnerType_>
1597 class ComplexFieldParser {
1598 public:
1599 template<typename Input2_>
1600 ParseInfo<std::complex<InnerType_> > operator()(Input2_& input, Index overall_line_count) {
1601 ParseInfo<std::complex<InnerType_> > output;
1602 output.remaining = true;
1603 std::complex<InnerType_> holding;
1604
1605 // Pulling out the real part first.
1606 temporary.clear();
1607 char x = input.get();
1608 while (1) {
1609 if (x == '\n') {
1610 if (temporary.empty()) {
1611 // This is the only place we need to check this error, as temporary is filled up immediately after this clause.
1612 throw std::runtime_error("empty real field on line " + std::to_string(overall_line_count + 1));
1613 } else {
1614 throw std::runtime_error("missing the imaginary part on line " + std::to_string(overall_line_count + 1));
1615 }
1616 }
1617
1618 temporary += x;
1619 if (!(input.advance())) {
1620 throw std::runtime_error("missing the imaginary part on line " + std::to_string(overall_line_count + 1));
1621 }
1622 x = input.get();
1623
1624 // We shift the blank space check here, as 'store()' is always called after chomping leading blanks;
1625 // so there wouldn't be any point putting this check at the start of the first loop iteration.
1626 if (x == ' ' || x == '\t') {
1627 if (!advance_and_chomp(input)) { // skipping past the current position before chomping.
1628 throw std::runtime_error("missing the imaginary part on line " + std::to_string(overall_line_count + 1));
1629 }
1630 if (input.get() == '\n') {
1631 throw std::runtime_error("missing the imaginary part on line " + std::to_string(overall_line_count + 1));
1632 }
1633 break;
1634 }
1635 }
1636 holding.real(convert_to_real<InnerType_>(temporary, overall_line_count));
1637
1638 // Now pulling out the imaginary part.
1639 temporary.clear();
1640 x = input.get();
1641 while (1) {
1642 temporary += x;
1643 if (!(input.advance())) {
1644 output.remaining = false;
1645 break;
1646 }
1647 x = input.get();
1648
1649 // Moving the newline check here, as the previous loop to parse the real part already precludes
1650 // the presence of a newline character before any other characters.
1651 if (x == '\n') {
1652 output.remaining = input.advance(); // skipping past the newline.
1653 break;
1654 }
1655
1656 // Also moving the blank space check here, as the scan for the imaginary component has already chomped
1657 // leading blanks; the first iteration of this loop cannot have any blanks before other characters.
1658 if (x == ' ' || x == '\t') {
1659 if (!advance_and_chomp(input)) { // skipping past the current position before chomping.
1660 output.remaining = false;
1661 break;
1662 }
1663 if (input.get() != '\n') {
1664 throw std::runtime_error("more fields than expected on line " + std::to_string(overall_line_count + 1));
1665 }
1666 output.remaining = input.advance(); // skipping past the newline.
1667 break;
1668 }
1669 }
1670 holding.imag(convert_to_real<InnerType_>(temporary, overall_line_count));
1671
1672 output.value = holding;
1673 return output;
1674 }
1675
1676 private:
1677 std::string temporary;
1678 };
1679
1680public:
1694 template<typename Type_ = double, class Store_>
1695 bool scan_complex(Store_ store) {
1696 check_preamble();
1697
1698 typedef std::complex<Type_> FullType;
1699 auto store_comp = [&](Index r, Index c, FullType val) -> bool {
1700 if constexpr(std::is_same<typename std::invoke_result<Store_, Index, Index, FullType>::type, bool>::value) {
1701 return store(r, c, val);
1702 } else {
1703 store(r, c, val);
1704 return true;
1705 }
1706 };
1707
1708 if (my_details.format == Format::COORDINATE) {
1709 if (my_details.object == Object::MATRIX) {
1710 return scan_matrix_coordinate_non_pattern<FullType, ComplexFieldParser<Type_> >(std::move(store_comp));
1711 } else {
1712 return scan_vector_coordinate_non_pattern<FullType, ComplexFieldParser<Type_> >(std::move(store_comp));
1713 }
1714 } else {
1715 if (my_details.object == Object::MATRIX) {
1716 return scan_matrix_array<FullType, ComplexFieldParser<Type_> >(std::move(store_comp));
1717 } else {
1718 return scan_vector_array<FullType, ComplexFieldParser<Type_> >(std::move(store_comp));
1719 }
1720 }
1721 }
1722
1738 template<typename Type_ = bool, class Store_>
1739 bool scan_pattern(Store_ store) {
1740 check_preamble();
1741 if (my_details.format != Format::COORDINATE) {
1742 throw std::runtime_error("'array' format for 'pattern' field is not supported");
1743 }
1744
1745 auto store_pat = [&](Index r, Index c) -> bool {
1746 if constexpr(std::is_same<typename std::invoke_result<Store_, Index, Index, bool>::type, bool>::value) {
1747 return store(r, c, true);
1748 } else {
1749 store(r, c, true);
1750 return true;
1751 }
1752 };
1753
1754 if (my_details.object == Object::MATRIX) {
1755 return scan_matrix_coordinate_pattern(std::move(store_pat));
1756 } else {
1757 return scan_vector_coordinate_pattern(std::move(store_pat));
1758 }
1759 }
1760};
1761
1762}
1763
1764#endif
Parse a matrix from a Matrix Market file.
Definition Parser.hpp:234
Index get_ncols() const
Definition Parser.hpp:713
bool scan_integer(Store_ store)
Definition Parser.hpp:1434
const MatrixDetails & get_banner() const
Definition Parser.hpp:537
bool scan_pattern(Store_ store)
Definition Parser.hpp:1739
bool scan_complex(Store_ store)
Definition Parser.hpp:1695
bool scan_real(Store_ &&store)
Definition Parser.hpp:1549
bool scan_double(Store_ store)
Definition Parser.hpp:1591
void scan_preamble()
Definition Parser.hpp:739
Parser(std::unique_ptr< Input_ > input, const ParserOptions &options)
Definition Parser.hpp:240
Index get_nrows() const
Definition Parser.hpp:699
Index get_nlines() const
Definition Parser.hpp:727
Classes and methods for parsing Matrix Market files.
unsigned long long Index
Definition Parser.hpp:31
Details extracted from the Matrix Market banner.
Definition utils.hpp:52
Symmetry symmetry
Definition utils.hpp:71
Format format
Definition utils.hpp:61
Object object
Definition utils.hpp:56
Field field
Definition utils.hpp:66
Options for the Parser constructor.
Definition Parser.hpp:36
std::size_t block_size
Definition Parser.hpp:47
int num_threads
Definition Parser.hpp:40
Utilities for matrix parsing.