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