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