xref: /minix/external/bsd/atf/dist/tools/parser.cpp (revision 0a6a1f1d)
1 //
2 // Automated Testing Framework (atf)
3 //
4 // Copyright (c) 2007 The NetBSD Foundation, Inc.
5 // All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 // 1. Redistributions of source code must retain the above copyright
11 //    notice, this list of conditions and the following disclaimer.
12 // 2. Redistributions in binary form must reproduce the above copyright
13 //    notice, this list of conditions and the following disclaimer in the
14 //    documentation and/or other materials provided with the distribution.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND
17 // CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
18 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 // IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY
21 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25 // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27 // IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 
30 #include <cassert>
31 #include <sstream>
32 
33 #include "parser.hpp"
34 #include "text.hpp"
35 
36 namespace impl = tools::parser;
37 #define IMPL_NAME "tools::parser"
38 
39 // ------------------------------------------------------------------------
40 // The "parse_error" class.
41 // ------------------------------------------------------------------------
42 
parse_error(size_t line,std::string msg)43 impl::parse_error::parse_error(size_t line, std::string msg) :
44     std::runtime_error(msg),
45     std::pair< size_t, std::string >(line, msg)
46 {
47 }
48 
~parse_error(void)49 impl::parse_error::~parse_error(void)
50     throw()
51 {
52 }
53 
54 const char*
what(void) const55 impl::parse_error::what(void)
56     const throw()
57 {
58     try {
59         std::ostringstream oss;
60         oss << "LONELY PARSE ERROR: " << first << ": " << second;
61         m_msg = oss.str();
62         return m_msg.c_str();
63     } catch (...) {
64         return "Could not format message for parsing error.";
65     }
66 }
67 
operator std::string(void) const68 impl::parse_error::operator std::string(void)
69     const
70 {
71     return tools::text::to_string(first) + ": " + second;
72 }
73 
74 // ------------------------------------------------------------------------
75 // The "parse_errors" class.
76 // ------------------------------------------------------------------------
77 
parse_errors(void)78 impl::parse_errors::parse_errors(void) :
79     std::runtime_error("No parsing errors yet")
80 {
81     m_msg.clear();
82 }
83 
~parse_errors(void)84 impl::parse_errors::~parse_errors(void)
85     throw()
86 {
87 }
88 
89 const char*
what(void) const90 impl::parse_errors::what(void)
91     const throw()
92 {
93     try {
94         m_msg = tools::text::join(*this, "\n");
95         return m_msg.c_str();
96     } catch (...) {
97         return "Could not format messages for parsing errors.";
98     }
99 }
100 
101 // ------------------------------------------------------------------------
102 // The "format_error" class.
103 // ------------------------------------------------------------------------
104 
format_error(const std::string & w)105 impl::format_error::format_error(const std::string& w) :
106     std::runtime_error(w.c_str())
107 {
108 }
109 
110 // ------------------------------------------------------------------------
111 // The "token" class.
112 // ------------------------------------------------------------------------
113 
token(void)114 impl::token::token(void) :
115     m_inited(false)
116 {
117 }
118 
token(size_t p_line,const token_type & p_type,const std::string & p_text)119 impl::token::token(size_t p_line,
120                    const token_type& p_type,
121                    const std::string& p_text) :
122     m_inited(true),
123     m_line(p_line),
124     m_type(p_type),
125     m_text(p_text)
126 {
127 }
128 
129 size_t
lineno(void) const130 impl::token::lineno(void)
131     const
132 {
133     return m_line;
134 }
135 
136 const impl::token_type&
type(void) const137 impl::token::type(void)
138     const
139 {
140     return m_type;
141 }
142 
143 const std::string&
text(void) const144 impl::token::text(void)
145     const
146 {
147     return m_text;
148 }
149 
operator bool(void) const150 impl::token::operator bool(void)
151     const
152 {
153     return m_inited;
154 }
155 
156 bool
operator !(void) const157 impl::token::operator!(void)
158     const
159 {
160     return !m_inited;
161 }
162 
163 // ------------------------------------------------------------------------
164 // The "header_entry" class.
165 // ------------------------------------------------------------------------
166 
header_entry(void)167 impl::header_entry::header_entry(void)
168 {
169 }
170 
header_entry(const std::string & n,const std::string & v,attrs_map as)171 impl::header_entry::header_entry(const std::string& n, const std::string& v,
172                                  attrs_map as) :
173     m_name(n),
174     m_value(v),
175     m_attrs(as)
176 {
177 }
178 
179 const std::string&
name(void) const180 impl::header_entry::name(void) const
181 {
182     return m_name;
183 }
184 
185 const std::string&
value(void) const186 impl::header_entry::value(void) const
187 {
188     return m_value;
189 }
190 
191 const impl::attrs_map&
attrs(void) const192 impl::header_entry::attrs(void) const
193 {
194     return m_attrs;
195 }
196 
197 bool
has_attr(const std::string & n) const198 impl::header_entry::has_attr(const std::string& n) const
199 {
200     return m_attrs.find(n) != m_attrs.end();
201 }
202 
203 const std::string&
get_attr(const std::string & n) const204 impl::header_entry::get_attr(const std::string& n) const
205 {
206     attrs_map::const_iterator iter = m_attrs.find(n);
207     assert(iter != m_attrs.end());
208     return (*iter).second;
209 }
210 
211 // ------------------------------------------------------------------------
212 // The header tokenizer.
213 // ------------------------------------------------------------------------
214 
215 namespace header {
216 
217 static const impl::token_type eof_type = 0;
218 static const impl::token_type nl_type = 1;
219 static const impl::token_type text_type = 2;
220 static const impl::token_type colon_type = 3;
221 static const impl::token_type semicolon_type = 4;
222 static const impl::token_type dblquote_type = 5;
223 static const impl::token_type equal_type = 6;
224 
225 class tokenizer : public impl::tokenizer< std::istream > {
226 public:
tokenizer(std::istream & is,size_t curline)227     tokenizer(std::istream& is, size_t curline) :
228         impl::tokenizer< std::istream >
229             (is, true, eof_type, nl_type, text_type, curline)
230     {
231         add_delim(';', semicolon_type);
232         add_delim(':', colon_type);
233         add_delim('=', equal_type);
234         add_quote('"', dblquote_type);
235     }
236 };
237 
238 static
239 impl::parser< header::tokenizer >&
read(impl::parser<header::tokenizer> & p,impl::header_entry & he)240 read(impl::parser< header::tokenizer >& p, impl::header_entry& he)
241 {
242     using namespace header;
243 
244     impl::token t = p.expect(text_type, nl_type, "a header name");
245     if (t.type() == nl_type) {
246         he = impl::header_entry();
247         return p;
248     }
249     std::string hdr_name = t.text();
250 
251     t = p.expect(colon_type, "`:'");
252 
253     t = p.expect(text_type, "a textual value");
254     std::string hdr_value = t.text();
255 
256     impl::attrs_map attrs;
257 
258     for (;;) {
259         t = p.expect(eof_type, semicolon_type, nl_type,
260                      "eof, `;' or new line");
261         if (t.type() == eof_type || t.type() == nl_type)
262             break;
263 
264         t = p.expect(text_type, "an attribute name");
265         std::string attr_name = t.text();
266 
267         t = p.expect(equal_type, "`='");
268 
269         t = p.expect(text_type, "word or quoted string");
270         std::string attr_value = t.text();
271         attrs[attr_name] = attr_value;
272     }
273 
274     he = impl::header_entry(hdr_name, hdr_value, attrs);
275 
276     return p;
277 }
278 
279 static
280 std::ostream&
write(std::ostream & os,const impl::header_entry & he)281 write(std::ostream& os, const impl::header_entry& he)
282 {
283     std::string line = he.name() + ": " + he.value();
284     impl::attrs_map as = he.attrs();
285     for (impl::attrs_map::const_iterator iter = as.begin(); iter != as.end();
286          iter++) {
287         assert((*iter).second.find('\"') == std::string::npos);
288         line += "; " + (*iter).first + "=\"" + (*iter).second + "\"";
289     }
290 
291     os << line << "\n";
292 
293     return os;
294 }
295 
296 } // namespace header
297 
298 // ------------------------------------------------------------------------
299 // Free functions.
300 // ------------------------------------------------------------------------
301 
302 std::pair< size_t, impl::headers_map >
read_headers(std::istream & is,size_t curline)303 impl::read_headers(std::istream& is, size_t curline)
304 {
305     using impl::format_error;
306 
307     headers_map hm;
308 
309     //
310     // Grammar
311     //
312     // header = entry+ nl
313     // entry = line nl
314     // line = text colon text
315     //        (semicolon (text equal (text | dblquote string dblquote)))*
316     // string = quoted_string
317     //
318 
319     header::tokenizer tkz(is, curline);
320     impl::parser< header::tokenizer > p(tkz);
321 
322     bool first = true;
323     for (;;) {
324         try {
325             header_entry he;
326             if (!header::read(p, he).good() || he.name().empty())
327                 break;
328 
329             if (first && he.name() != "Content-Type")
330                 throw format_error("Could not determine content type");
331             else
332                 first = false;
333 
334             hm[he.name()] = he;
335         } catch (const impl::parse_error& pe) {
336             p.add_error(pe);
337             p.reset(header::nl_type);
338         }
339     }
340 
341     if (!is.good())
342         throw format_error("Unexpected end of stream");
343 
344     return std::pair< size_t, headers_map >(tkz.lineno(), hm);
345 }
346 
347 void
write_headers(const impl::headers_map & hm,std::ostream & os)348 impl::write_headers(const impl::headers_map& hm, std::ostream& os)
349 {
350     assert(!hm.empty());
351     headers_map::const_iterator ct = hm.find("Content-Type");
352     assert(ct != hm.end());
353     header::write(os, (*ct).second);
354     for (headers_map::const_iterator iter = hm.begin(); iter != hm.end();
355          iter++) {
356         if ((*iter).first != "Content-Type")
357             header::write(os, (*iter).second);
358     }
359     os << "\n";
360 }
361 
362 void
validate_content_type(const impl::headers_map & hm,const std::string & fmt,int version)363 impl::validate_content_type(const impl::headers_map& hm, const std::string& fmt,
364                             int version)
365 {
366     using impl::format_error;
367 
368     headers_map::const_iterator iter = hm.find("Content-Type");
369     if (iter == hm.end())
370         throw format_error("Could not determine content type");
371 
372     const header_entry& he = (*iter).second;
373     if (he.value() != fmt)
374         throw format_error("Mismatched content type: expected `" + fmt +
375                            "' but got `" + he.value() + "'");
376 
377     if (!he.has_attr("version"))
378         throw format_error("Could not determine version");
379     const std::string& vstr = tools::text::to_string(version);
380     if (he.get_attr("version") != vstr)
381         throw format_error("Mismatched version: expected `" +
382                            vstr + "' but got `" +
383                            he.get_attr("version") + "'");
384 }
385