eminem
Parse Matrix Market files in C++
Loading...
Searching...
No Matches
Parser.hpp
Go to the documentation of this file.
1#ifndef EMINEM_PARSER_HPP
2#define EMINEM_PARSER_HPP
3
4#include <vector>
5#include <string>
6#include <cstring>
7#include <complex>
8#include <type_traits>
9
10#include "byteme/PerByte.hpp"
11#include "utils.hpp"
12
19namespace eminem {
20
28template<bool parallel_ = false, class Pointer_ = byteme::Reader*>
29class Parser {
30public:
34 Parser(Pointer_ r) : input(std::move(r)) {}
35
36private:
37 typename std::conditional<parallel_, byteme::PerByteParallel<char, Pointer_>, byteme::PerByte<char, Pointer_> >::type input;
38 size_t current_line = 0;
39
40private:
41 bool passed_banner = false;
42 MatrixDetails details;
43
44 void parse_banner(const std::string& contents) {
45 size_t pos = 13; // skip the "MatrixMarket " string.
46
47 auto obj_str = contents.c_str() + pos;
48 if (std::strncmp(obj_str, "matrix ", 7) == 0) {
49 details.object = Object::MATRIX;
50 pos += 7;
51 } else if (std::strncmp(obj_str, "vector ", 7) == 0) {
52 details.object = Object::VECTOR;
53 pos += 7;
54 } else {
55 throw std::runtime_error("first banner field should be one of 'matrix' or 'vector'");
56 }
57
58 auto format_str = contents.c_str() + pos;
59 if (std::strncmp(format_str, "coordinate ", 11) == 0) {
60 details.format = Format::COORDINATE;
61 pos += 11;
62 } else if (std::strncmp(format_str, "array ", 6) == 0) {
63 details.format = Format::ARRAY;
64 pos += 6;
65 } else {
66 throw std::runtime_error("second banner field should be one of 'coordinate' or 'array'");
67 }
68
69 auto field_str = contents.c_str() + pos;
70 if (std::strncmp(field_str, "integer ", 8) == 0) {
71 details.field = Field::INTEGER;
72 pos += 8;
73 } else if (std::strncmp(field_str, "double ", 7) == 0) {
74 details.field = Field::DOUBLE;
75 pos += 7;
76 } else if (std::strncmp(field_str, "complex ", 8) == 0) {
77 details.field = Field::COMPLEX;
78 pos += 8;
79 } else if (std::strncmp(field_str, "pattern ", 8) == 0) {
80 details.field = Field::PATTERN;
81 pos += 8;
82 } else if (std::strncmp(field_str, "real ", 5) == 0) {
83 details.field = Field::REAL;
84 pos += 5;
85 } else {
86 throw std::runtime_error("third banner field should be one of 'real', 'integer', 'double', 'complex' or 'pattern'");
87 }
88
89 auto sym_str = contents.c_str() + pos;
90 if (std::strcmp(sym_str, "general") == 0) {
91 details.symmetry = Symmetry::GENERAL;
92 } else if (std::strcmp(sym_str, "symmetric") == 0) {
93 details.symmetry = Symmetry::SYMMETRIC;
94 } else if (std::strcmp(sym_str, "skew-symmetric") == 0) {
95 details.symmetry = Symmetry::SKEW_SYMMETRIC;
96 } else if (std::strcmp(sym_str, "hermitian") == 0) {
97 details.symmetry = Symmetry::HERMITIAN;
98 } else {
99 throw std::runtime_error("fourth banner field should be one of 'general', 'symmetric', 'skew-symemtric' or 'hermitian'");
100 }
101 }
102
103 void scan_banner() {
104 std::string contents;
105 bool valid = input.valid();
106 if (passed_banner) {
107 throw std::runtime_error("banner has already been scanned");
108 }
109
110 while (valid) {
111 if (input.get() != '%') {
112 throw std::runtime_error("failed to find banner line before non-commented line " + std::to_string(current_line + 1));
113 }
114
115 // Exhaust all leading comment characters.
116 do {
117 valid = input.advance();
118 } while (valid && input.get() == '%');
119
120 // Inserting up to the next line.
121 while (valid && input.get() != '\n') {
122 contents += input.get();
123 valid = input.advance();
124 }
125
126 if (!valid) {
127 break;
128 }
129 ++current_line;
130 valid = input.advance();
131
132 if (contents.rfind("MatrixMarket ", 0) == 0) {
133 parse_banner(contents);
134 passed_banner = true;
135 return;
136 }
137 contents.clear();
138 }
139
140 throw std::runtime_error("failed to find banner line before end of file");
141 }
142
143public:
150 const MatrixDetails& get_banner() const {
151 if (!passed_banner) {
152 throw std::runtime_error("banner has not yet been scanned");
153 }
154 return details;
155 }
156
157private:
158 bool passed_size = false;
159 size_t nrows = 0, ncols = 0, nlines = 0;
160
161 void check_size(int onto, bool non_empty) {
162 if (!non_empty) {
163 throw std::runtime_error("detected an empty size field on line " + std::to_string(current_line + 1));
164 }
165
166 if (details.object == Object::MATRIX) {
167 if (details.format == Format::COORDINATE) {
168 if (onto != 2) {
169 throw std::runtime_error("expected three size fields for coordinate matrices on line " + std::to_string(current_line + 1));
170 }
171 } else if (details.format == Format::ARRAY) {
172 if (onto != 1) {
173 throw std::runtime_error("expected two size fields for array matrices on line " + std::to_string(current_line + 1));
174 }
175 nlines = nrows * ncols;
176 }
177 } else {
178 if (details.format == Format::COORDINATE) {
179 if (onto != 1) {
180 throw std::runtime_error("expected two size fields for coordinate vectors on line " + std::to_string(current_line + 1));
181 }
182 nlines = ncols;
183 } else if (details.format == Format::ARRAY) {
184 if (onto != 0) {
185 throw std::runtime_error("expected one size field for array vectors on line " + std::to_string(current_line + 1));
186 }
187 nlines = nrows;
188 }
189 ncols = 1;
190 }
191
192 passed_size = true;
193 }
194
195 void scan_size() {
196 char onto = 0;
197 bool non_empty = false;
198 bool valid = input.valid();
199
200 while (valid) {
201 if (input.get() == '%') {
202 // Handling stray comments... try to get to the next line as quickly as possible.
203 do {
204 valid = input.advance();
205 } while (valid && input.get() != '\n');
206
207 if (!valid) {
208 break;
209 } else {
210 ++current_line;
211 valid = input.advance();
212 continue;
213 }
214 }
215
216 // Chomping digits.
217 do {
218 char current = input.get();
219 switch(current) {
220 case ' ':
221 if (!non_empty) {
222 throw std::runtime_error("detected an empty size field on line " + std::to_string(current_line + 1));
223 }
224 ++onto;
225 break;
226
227 case '\n':
228 ++current_line;
229 valid = input.advance();
230 check_size(onto, non_empty);
231 return;
232
233 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
234 {
235 non_empty = true;
236 auto delta = current - '0';
237 switch(onto) {
238 case 0:
239 nrows *= 10;
240 nrows += delta;
241 break;
242 case 1:
243 ncols *= 10;
244 ncols += delta;
245 break;
246 case 2:
247 nlines *= 10;
248 nlines += delta;
249 break;
250 }
251 }
252 break;
253
254 default:
255 throw std::runtime_error("only non-negative integers should be present on line " + std::to_string(current_line + 1));
256 }
257
258 valid = input.advance();
259 } while (valid);
260 }
261
262 check_size(onto, non_empty);
263 }
264
265public:
273 size_t get_nrows() const {
274 if (!passed_size) {
275 throw std::runtime_error("size line has not yet been scanned");
276 }
277 return nrows;
278 }
279
287 size_t get_ncols() const {
288 if (!passed_size) {
289 throw std::runtime_error("size line has not yet been scanned");
290 }
291 return ncols;
292 }
293
301 size_t get_nlines() const {
302 if (!passed_size) {
303 throw std::runtime_error("size line has not yet been scanned");
304 }
305 return nlines;
306 }
307
308public:
314 scan_banner();
315 scan_size();
316 return;
317 }
318
319private:
320 void check_coordinate_common(size_t currow, size_t current_data_line, bool non_empty) const {
321 if (!non_empty) {
322 throw std::runtime_error("empty field detected on line " + std::to_string(current_line + 1));
323 }
324
325 if (current_data_line >= nlines) {
326 throw std::runtime_error("more lines present than specified in the header (" + std::to_string(nlines) + ")");
327 }
328
329 if (!currow) {
330 throw std::runtime_error("row index must be positive on line " + std::to_string(current_line + 1));
331 }
332 if (currow > nrows) {
333 throw std::runtime_error("row index out of range on line " + std::to_string(current_line + 1));
334 }
335 }
336
337 template<Field field_>
338 void check_coordinate_matrix(size_t currow, size_t curcol, size_t current_data_line, int onto, bool non_empty) const {
339 check_coordinate_common(currow, current_data_line, non_empty);
340
341 if constexpr(field_ == Field::REAL || field_ == Field::DOUBLE || field_ == Field::INTEGER) {
342 if (onto != 2) {
343 throw std::runtime_error("expected 3 fields on line " + std::to_string(current_line + 1));
344 }
345 } else if constexpr(field_ == Field::PATTERN) {
346 if (onto != 1) {
347 throw std::runtime_error("expected 2 fields on line " + std::to_string(current_line + 1));
348 }
349 } else if constexpr(field_ == Field::COMPLEX) {
350 if (onto != 3) {
351 throw std::runtime_error("expected 4 fields on line " + std::to_string(current_line + 1));
352 }
353 }
354
355 if (!curcol) {
356 throw std::runtime_error("column index must be positive on line " + std::to_string(current_line + 1));
357 }
358 if (curcol > ncols) {
359 throw std::runtime_error("column index out of range on line " + std::to_string(current_line + 1));
360 }
361 }
362
363 template<Field field_>
364 void check_coordinate_vector(size_t currow, size_t current_data_line, int onto, bool non_empty) const {
365 check_coordinate_common(currow, current_data_line, non_empty);
366
367 if constexpr(field_ == Field::REAL || field_ == Field::DOUBLE || field_ == Field::INTEGER) {
368 if (onto != 1) {
369 throw std::runtime_error("expected 2 fields on line " + std::to_string(current_line + 1));
370 }
371 } else if constexpr(field_ == Field::PATTERN) {
372 if (onto != 0) {
373 throw std::runtime_error("expected 1 field on line " + std::to_string(current_line + 1));
374 }
375 } else if constexpr(field_ == Field::COMPLEX) {
376 if (onto != 2) {
377 throw std::runtime_error("expected 3 fields on line " + std::to_string(current_line + 1));
378 }
379 }
380 }
381
382 template<Object object_, Field field_, class Store_, class Compose_, class Bump_, class Finish_>
383 bool scan_coordinate(Store_ store, Compose_ compose, Bump_ bump, Finish_ finish) {
384 if (!passed_banner || !passed_size) {
385 throw std::runtime_error("banner or size lines have not yet been parsed");
386 }
387
388 size_t current_data_line = 0;
389 size_t currow = 0, curcol = 0;
390 char onto = 0;
391 bool non_empty = false;
392 bool valid = input.valid();
393
394 if constexpr(object_ == Object::VECTOR) {
395 curcol = 1;
396 }
397
398 typedef typename std::invoke_result<Finish_, size_t>::type finish_value;
399 constexpr bool can_quit = std::is_same<typename std::invoke_result<Store_, size_t, size_t, finish_value>::type, bool>::value;
400
401 while (valid) {
402 if (input.get() == '%') {
403 // Try to get quickly to the next line.
404 do {
405 valid = input.advance();
406 } while (valid && input.get() != '\n');
407
408 if (!valid) {
409 break;
410 } else {
411 ++current_line;
412 valid = input.advance();
413 continue;
414 }
415 }
416
417 do {
418 char current = input.get();
419 if (current == ' ') {
420 if (!non_empty) {
421 throw std::runtime_error("detected empty field on line " + std::to_string(current_line + 1));
422 }
423 if constexpr(object_ == Object::MATRIX) {
424 if (onto >= 2) {
425 bump(current_line);
426 }
427 } else {
428 if (onto >= 1) {
429 bump(current_line);
430 }
431 }
432 ++onto;
433 non_empty = false;
434
435 } else if (current == '\n') {
436 if constexpr(object_ == Object::MATRIX) {
437 check_coordinate_matrix<field_>(currow, curcol, current_data_line, onto, non_empty);
438 } else {
439 check_coordinate_vector<field_>(currow, current_data_line, onto, non_empty);
440 }
441
442 if constexpr(can_quit) {
443 if (!store(currow, curcol, finish(current_line))) {
444 return false;
445 }
446 } else {
447 store(currow, curcol, finish(current_line));
448 }
449
450 currow = 0;
451 if constexpr(object_ == Object::MATRIX) {
452 curcol = 0;
453 }
454 ++current_data_line;
455 onto = 0;
456 non_empty = false;
457 ++current_line;
458 valid = input.advance();
459 break;
460
461 } else {
462 switch (onto) {
463 case 0:
464 if (current < '0' || current > '9') {
465 throw std::runtime_error("row index should be a non-negative integer on line " + std::to_string(current_line + 1));
466 }
467 currow *= 10;
468 currow += current - '0';
469 break;
470
471 case 1:
472 if constexpr(object_ == Object::MATRIX) {
473 if (current < '0' || current > '9') {
474 throw std::runtime_error("column index should be a non-negative integer on line " + std::to_string(current_line + 1));
475 }
476 curcol *= 10;
477 curcol += current - '0';
478 } else {
479 compose(current, current_line);
480 }
481 break;
482
483 default:
484 compose(current, current_line);
485 break;
486 }
487 non_empty = true;
488 }
489
490 valid = input.advance();
491 } while (valid);
492 }
493
494 // If onto = 0 and non_empty = false, we ended on a newline, so
495 // there's no extra entry to add. Otherwise, we try to add the
496 // last line that was _not_ terminated by a newline.
497 if (onto != 0 || non_empty) {
498 if constexpr(object_ == Object::MATRIX) {
499 check_coordinate_matrix<field_>(currow, curcol, current_data_line, onto, non_empty);
500 } else {
501 check_coordinate_vector<field_>(currow, current_data_line, onto, non_empty);
502 }
503
504 if constexpr(can_quit) {
505 if (!store(currow, curcol, finish(current_line))) {
506 return false;
507 }
508 } else {
509 store(currow, curcol, finish(current_line));
510 }
511
512 ++current_data_line;
513 }
514
515 if (current_data_line != nlines) {
516 throw std::runtime_error("fewer lines present than specified in the header (" + std::to_string(nlines) + ")");
517 }
518 return true;
519 }
520
521private:
522 template<Field field_>
523 void check_array(size_t current_data_line, int onto, bool non_empty) const {
524 if (!non_empty) {
525 throw std::runtime_error("empty field detected on line " + std::to_string(current_line + 1));
526 }
527
528 if constexpr(field_ == Field::REAL || field_ == Field::DOUBLE || field_ == Field::INTEGER) {
529 if (onto != 0) {
530 throw std::runtime_error("expected 1 field on line " + std::to_string(current_line + 1));
531 }
532 } else if constexpr(field_ == Field::COMPLEX) {
533 if (onto != 1) {
534 throw std::runtime_error("expected 2 fields on line " + std::to_string(current_line + 1));
535 }
536 }
537
538 if (current_data_line >= nlines) {
539 throw std::runtime_error("more lines present than expected for an array format (" + std::to_string(nlines) + ")");
540 }
541 }
542
543 template<Field field_, class Store_, class Compose_, class Bump_, class Finish_>
544 bool scan_array(Store_ store, Compose_ compose, Bump_ bump, Finish_ finish) {
545 if (!passed_banner || !passed_size) {
546 throw std::runtime_error("banner or size lines have not yet been parsed");
547 }
548
549 size_t current_data_line = 0;
550 size_t currow = 1, curcol = 1;
551 char onto = 0;
552 bool non_empty = false;
553 bool valid = input.valid();
554
555 typedef typename std::invoke_result<Finish_, size_t>::type finish_value;
556 constexpr bool can_quit = std::is_same<typename std::invoke_result<Store_, size_t, size_t, finish_value>::type, bool>::value;
557
558 while (valid) {
559 if (input.get() == '%') {
560 // Try to get quickly to the next line.
561 do {
562 valid = input.advance();
563 } while (valid && input.get() != '\n');
564
565 if (!valid) {
566 break;
567 } else {
568 ++current_line;
569 valid = input.advance();
570 continue;
571 }
572 }
573
574 do {
575 char current = input.get();
576 if (current == ' ') {
577 if (!non_empty) {
578 throw std::runtime_error("detected empty field on line " + std::to_string(current_line + 1));
579 }
580 bump(current_line);
581 ++onto;
582 non_empty = false;
583
584 } else if (current == '\n') {
585 check_array<field_>(current_data_line, onto, non_empty);
586 if constexpr(can_quit) {
587 if (!store(currow, curcol, finish(current_line))) {
588 return false;
589 }
590 } else {
591 store(currow, curcol, finish(current_line));
592 }
593
594 ++currow;
595 if (currow > nrows) {
596 ++curcol;
597 currow = 1;
598 }
599 ++current_data_line;
600 ++current_line;
601 onto = 0;
602 non_empty = false;
603
604 valid = input.advance();
605 break;
606
607 } else {
608 compose(current, current_line);
609 non_empty = true;
610 }
611
612 valid = input.advance();
613 } while (valid);
614 }
615
616 // If onto = 0 and non_empty = false, we ended on a newline, so
617 // there's no extra entry to add. Otherwise, we try to add the
618 // last line that was _not_ terminated by a newline.
619 if (onto != 0 || non_empty) {
620 check_array<field_>(current_data_line, onto, non_empty);
621 if constexpr(can_quit) {
622 if (!store(currow, curcol, finish(current_line))) {
623 return false;
624 }
625 } else {
626 store(currow, curcol, finish(current_line));
627 }
628 ++current_data_line;
629 }
630
631 if (current_data_line != nlines) {
632 throw std::runtime_error("fewer lines present than expected for an array format (" + std::to_string(nlines) + ")");
633 }
634 return true;
635 }
636
637private:
638 template<typename Type_>
639 static Type_ convert_to_real(const std::string& temporary, size_t line) {
640 Type_ output;
641 size_t n = 0;
642
643 try {
644 if constexpr(std::is_same<Type_, float>::value) {
645 output = std::stof(temporary, &n);
646 } else if constexpr(std::is_same<Type_, long double>::value) {
647 output = std::stold(temporary, &n);
648 } else {
649 output = std::stod(temporary, &n);
650 }
651 } catch (std::invalid_argument& e) {
652 throw std::runtime_error("failed to convert value to a real number on line " + std::to_string(line + 1));
653 }
654
655 if (n != temporary.size()) {
656 throw std::runtime_error("failed to convert value to a real number on line " + std::to_string(line + 1));
657 }
658
659 return output;
660 }
661
662public:
676 template<typename Type_ = int, class Store_>
677 bool scan_integer(Store_&& store) {
678 bool init = true;
679 bool negative = false;
680 Type_ curval = 0;
681
682 auto compose = [&](char x, size_t line) -> void {
683 if (init) {
684 if (x == '-') {
685 negative = true;
686 return;
687 }
688 init = false;
689 }
690 if (x < '0' || x > '9') {
691 throw std::runtime_error("expected an integer value on line " + std::to_string(line + 1));
692 }
693 curval *= 10;
694 curval += (x - '0');
695 };
696
697 auto bump = [](size_t) -> void {};
698
699 auto finish = [&](size_t) -> Type_ {
700 if (negative) {
701 curval *= -1;
702 }
703 init = true;
704 negative = false;
705
706 auto copy = curval;
707 curval = 0;
708 return copy;
709 };
710
711 if (details.format == Format::COORDINATE) {
712 if (details.object == Object::MATRIX) {
713 return scan_coordinate<Object::MATRIX, Field::INTEGER>(std::forward<Store_>(store), std::move(compose), std::move(bump), std::move(finish));
714 } else {
715 return scan_coordinate<Object::VECTOR, Field::INTEGER>(std::forward<Store_>(store), std::move(compose), std::move(bump), std::move(finish));
716 }
717 } else {
718 return scan_array<Field::INTEGER>(std::forward<Store_>(store), std::move(compose), std::move(bump), std::move(finish));
719 }
720 }
721
735 template<typename Type_ = double, class Store_>
736 bool scan_real(Store_&& store) {
737 std::string temporary;
738
739 auto compose = [&](char x, size_t line) -> void {
740 if (std::isspace(x)) {
741 throw std::runtime_error("detected whitespace in value on line " + std::to_string(line + 1));
742 }
743 temporary += x;
744 };
745
746 auto bump = [](size_t) -> void {};
747
748 auto finish = [&](size_t line) -> double {
749 double output = convert_to_real<Type_>(temporary, line);
750 temporary.clear();
751 return output;
752 };
753
754 if (details.format == Format::COORDINATE) {
755 if (details.object == Object::MATRIX) {
756 return scan_coordinate<Object::MATRIX, Field::REAL>(std::forward<Store_>(store), std::move(compose), std::move(bump), std::move(finish));
757 } else {
758 return scan_coordinate<Object::VECTOR, Field::REAL>(std::forward<Store_>(store), std::move(compose), std::move(bump), std::move(finish));
759 }
760 } else {
761 return scan_array<Field::REAL>(std::forward<Store_>(store), std::move(compose), std::move(bump), std::move(finish));
762 }
763 }
764
779 template<typename Type_ = double, class Store_>
780 bool scan_double(Store_&& store) {
781 return scan_real<Type_, Store_>(std::forward<Store_>(store));
782 }
783
797 template<typename Type_ = double, class Store_>
798 bool scan_complex(Store_&& store) {
799 std::string temporary;
800 std::complex<Type_> holding;
801
802 auto compose = [&](char x, size_t line) -> void {
803 if (std::isspace(x)) {
804 throw std::runtime_error("detected whitespace in value on line " + std::to_string(line + 1));
805 }
806 temporary += x;
807 };
808
809 auto bump = [&](size_t line) -> void {
810 holding.real(convert_to_real<Type_>(temporary, line));
811 temporary.clear();
812 };
813
814 auto finish = [&](size_t line) -> std::complex<Type_> {
815 holding.imag(convert_to_real<Type_>(temporary, line));
816 temporary.clear();
817 return holding;
818 };
819
820 if (details.format == Format::COORDINATE) {
821 if (details.object == Object::MATRIX) {
822 return scan_coordinate<Object::MATRIX, Field::COMPLEX>(std::forward<Store_>(store), std::move(compose), std::move(bump), std::move(finish));
823 } else {
824 return scan_coordinate<Object::VECTOR, Field::COMPLEX>(std::forward<Store_>(store), std::move(compose), std::move(bump), std::move(finish));
825 }
826 } else {
827 return scan_array<Field::COMPLEX>(std::forward<Store_>(store), std::move(compose), std::move(bump), std::move(finish));
828 }
829 }
830
846 template<typename Type_ = bool, class Store_>
847 bool scan_pattern(Store_&& store) {
848 auto compose = [](char, size_t) -> void {};
849 auto bump = [](size_t) -> void {};
850 auto finish = [](size_t) -> Type_ {
851 return true;
852 };
853
854 if (details.format != Format::COORDINATE) {
855 throw std::runtime_error("'array' format for 'pattern' field is not supported");
856 }
857
858 if (details.object == Object::MATRIX) {
859 return scan_coordinate<Object::MATRIX, Field::PATTERN>(std::forward<Store_>(store), std::move(compose), std::move(bump), std::move(finish));
860 } else {
861 return scan_coordinate<Object::VECTOR, Field::PATTERN>(std::forward<Store_>(store), std::move(compose), std::move(bump), std::move(finish));
862 }
863 }
864};
865
866}
867
868#endif
Parse a matrix from a Matrix Market file.
Definition: Parser.hpp:29
bool scan_integer(Store_ &&store)
Definition: Parser.hpp:677
bool scan_complex(Store_ &&store)
Definition: Parser.hpp:798
bool scan_double(Store_ &&store)
Definition: Parser.hpp:780
bool scan_pattern(Store_ &&store)
Definition: Parser.hpp:847
void scan_preamble()
Definition: Parser.hpp:313
size_t get_ncols() const
Definition: Parser.hpp:287
bool scan_real(Store_ &&store)
Definition: Parser.hpp:736
Parser(Pointer_ r)
Definition: Parser.hpp:34
const MatrixDetails & get_banner() const
Definition: Parser.hpp:150
size_t get_nrows() const
Definition: Parser.hpp:273
size_t get_nlines() const
Definition: Parser.hpp:301
Classes and methods for parsing Matrix Market files.
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
Utilities for matrix parsing.