Branch data Line data Source code
1 : : // *****************************************************************************
2 : : /*!
3 : : \file src/Control/FileParser.cpp
4 : : \copyright 2012-2015 J. Bakosi,
5 : : 2016-2018 Los Alamos National Security, LLC.,
6 : : 2019-2021 Triad National Security, LLC.
7 : : All rights reserved. See the LICENSE file for details.
8 : : \brief File parser base class definition
9 : : \details File parser base class defintion. File parser base serves as a
10 : : base class for various file parsers, e.g., input deck parsers. It does
11 : : generic low-level I/O, e.g., testing whether the file to be parsed exits or
12 : : not and associated error handling, as well as after-parser diagnostics.
13 : : */
14 : : // *****************************************************************************
15 : :
16 : : #include <map>
17 : :
18 : : #include "FileParser.hpp"
19 : : #include "Exception.hpp"
20 : : #include "Reader.hpp"
21 : : #include "Print.hpp"
22 : :
23 : : using tk::FileParser;
24 : :
25 : 318 : FileParser::FileParser( const std::string& filename ) : m_filename( filename )
26 : : // *****************************************************************************
27 : : // Constructor
28 : : //! \param[in] filename File to be parsed by the parser
29 : : //! \details This constructor does basic tests in an attempt to determine if the
30 : : //! file to be parsed exists and is in good shape and does associated error
31 : : //! handling. This file stream is local, only used for error checking, and
32 : : //! thus is not part of the object state here since the parser, inheriting
33 : : //! from FileParser, e.g., walker::InputDeckParser, parses by completely
34 : : //! outsourcing the parsing (to PEGTL), so there is no need to store the file
35 : : //! stream handle here.
36 : : // *****************************************************************************
37 : : {
38 : : // Make sure there is a filename
39 : : Assert( !filename.empty(), "No filename specified" );
40 : :
41 : : // Local file stream handle
42 [ + - ]: 636 : std::ifstream q;
43 : :
44 : : // Check if file exists, throw exception if it does not
45 [ + - ]: 318 : q.open( filename, std::ifstream::in );
46 [ + + ][ + - ]: 320 : ErrChk( q.good(), "Failed to open file: " + filename );
[ + - ][ + - ]
[ - + ][ - + ]
47 : :
48 : : // Attempt to read a character, throw if it fails
49 : : // It is curious that on some systems opening a directory instead of a file
50 : : // with the above ifstream::open() call does not set the failbit. Thus we get
51 : : // here fine, so we try to read a character from it. If it is a directory or
52 : : // an empty file the read will fail, so we throw. Read more at: http://
53 : : // stackoverflow.com/questions/9591036/
54 : : // ifstream-open-doesnt-set-error-bits-when-argument-is-a-directory.
55 [ + - ]: 316 : q.get();
56 [ + + ][ + - ]: 317 : ErrChk( q.good(), "Failed to read from file: " + filename );
[ + - ][ + - ]
[ - + ][ - + ]
57 : :
58 : : // Close it
59 [ + - ]: 315 : q.close();
60 [ - + ][ - - ]: 315 : ErrChk( !q.fail(), "Failed to close file: " + filename );
[ - - ][ - - ]
[ - - ][ - - ]
61 : 315 : }
62 : :
63 : : void
64 : 314 : FileParser::diagnostics( const tk::Print& print,
65 : : const std::vector< std::string >& messages )
66 : : // *****************************************************************************
67 : : // Echo errors and warnings accumulated during parsing
68 : : //! \param[in] print Pretty printer
69 : : //! \param[in] messages Vector of strings of errors and warnings
70 : : // *****************************************************************************
71 : : {
72 : : // Bundle storing multiple messages for a single errouneous line
73 : : struct ErroneousLine {
74 : : std::size_t dlnum; //!< number of digits of line num
75 : : std::string parsed; //!< original line parsed
76 : : std::string underline; //!< underline
77 : : std::vector< std::string > msg; //!< error or warning messages
78 : : explicit ErroneousLine() : dlnum(0), parsed(), underline(), msg() {}
79 : 0 : explicit ErroneousLine( const std::string& m ) :
80 [ - - ][ - - ]: 0 : dlnum(0), parsed(), underline(), msg({{m}}) {}
[ - - ][ - - ]
[ - - ][ - - ]
81 : : };
82 : :
83 : 628 : Reader id( m_filename ); // file reader for extracting erroneous lines
84 : : bool err = false; // signaling whether there were any errors
85 : : std::map< std::size_t, ErroneousLine > lines; // erroneous lines, key: lineno
86 : :
87 : : // Underline errors and warnings
88 [ + + ]: 317 : for (const auto& e : messages) {
89 : : // decide if error or warning
90 : : char underchar = ' ';
91 [ + + ]: 3 : if (e.find( "Error" ) != std::string::npos) { err = true; underchar = '^'; }
92 [ + + ]: 2 : else if (e.find( "Warning" ) != std::string::npos) underchar = '~';
93 : :
94 [ + + ]: 3 : if (underchar == '^' || underchar == '~') {
95 : : auto sloc = e.find( "at " );
96 [ - + ]: 2 : if (sloc != std::string::npos) { // if we have location info
97 : : // skip "at "
98 : 0 : sloc += 3;
99 : : // find a comma starting from after "at "
100 : : auto eloc = e.find_first_of( ',', sloc );
101 : : // extract line number of error from error message
102 [ - - ][ - - ]: 0 : const std::size_t lnum = std::stoul( e.substr( sloc, eloc-sloc ) );
103 : : // store number of digits in line number
104 : : const auto dlnum = eloc - sloc;
105 : : // find a dot starting from after "at "
106 : : eloc = e.find_first_of( '.', sloc );
107 : : // skip line number
108 : 0 : sloc = e.find_first_of( ',', sloc ) + 1;
109 : : // extract column number of error from error message
110 [ - - ]: 0 : const decltype(sloc) cnum = std::stoul( e.substr( sloc, eloc-sloc ) )-1;
111 : : // store erroneous line information in map
112 [ - - ]: 0 : auto& l = lines[ lnum ];
113 : : // store number of digits in line number
114 : 0 : l.dlnum = dlnum;
115 : : // get erroneous line from file and store
116 [ - - ]: 0 : l.parsed = id.line( lnum );
117 : : // store message
118 [ - - ]: 0 : l.msg.push_back( e );
119 : : // start constructing underline (from scratch if first error on line)
120 [ - - ][ - - ]: 0 : if (l.underline.empty()) l.underline = std::string(l.parsed.size(),' ');
121 : : // find beginning of erroneous argument, this can be found in either e
122 : : // (the full error message which may contain the erroneous substring
123 : : // between single quotes and can also contain white space), or in
124 : : // l.parsed (the erroneouss line from the file), if we find a
125 : : // singly-quoted substring in e, we find the location of that substring
126 : : // in l.parsed, if we don't find a singly-quoted substring in e,
127 : : // we reverse-search l.parsed until a white space, in which case the
128 : : // error that will be underlined will be a single word
129 : 0 : sloc = e.find( '\'' );
130 [ - - ]: 0 : if (sloc == std::string::npos) {
131 : 0 : sloc = l.parsed.rfind( ' ', cnum-1 );
132 : : } else {
133 : 0 : auto sloc2 = e.find( '\'', sloc+1 );
134 [ - - ][ - - ]: 0 : sloc = l.parsed.find( e.substr( sloc+1, sloc2-sloc-1 ) ) - 1;
135 : : }
136 : : // special-handle the beginning of the line with no space in front of it
137 [ - - ]: 0 : if (sloc == std::string::npos) sloc = 0; else ++sloc;
138 : : // underline error and warning differently
139 [ - - ]: 0 : for (auto i=sloc; i<l.underline.size(); ++i)
140 : 0 : l.underline[i] = underchar;
141 : : }
142 [ - + ][ - - ]: 2 : } else if (!e.empty()) lines.emplace( 0, ErroneousLine(e) );
[ - - ]
143 : : }
144 : :
145 : : // Output errors and warnings underlined to quiet stream and message
146 [ - + ]: 314 : for (const auto& l : lines) {
147 : : const auto& e = l.second;
148 : : print % '\n';
149 [ - - ]: 0 : print % ">>> Line " % l.first % ": '" % e.parsed % "'\n";
150 [ - - ]: 0 : print % ">>>" % std::string( e.dlnum+9, ' ' ) % e.underline % "\n";
151 [ - - ]: 0 : for (const auto& m : e.msg) print % ">>> " % m % '\n';
152 : : print % '\n';
153 : : }
154 : :
155 : : // Exit if there were any errors
156 [ + + ][ + - ]: 315 : if (err) Throw( "Error(s) occurred while parsing file " + m_filename );
[ + - ][ + - ]
[ - + ][ - + ]
157 : 313 : }
|