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