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