1 /*=============================================================================
2     Copyright (c) 2014 Joel de Guzman
3 
4     Distributed under the Boost Software License, Version 1.0. (See accompanying
5     file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 ==============================================================================*/
7 #if !defined(BOOST_SPIRIT_X3_ERROR_REPORTING_MAY_19_2014_00405PM)
8 #define BOOST_SPIRIT_X3_ERROR_REPORTING_MAY_19_2014_00405PM
9 
10 #include <boost/spirit/home/x3/support/ast/position_tagged.hpp>
11 #include <boost/spirit/home/x3/support/utility/utf8.hpp>
12 #include <ostream>
13 
14 // Clang-style error handling utilities
15 
16 namespace boost { namespace spirit { namespace x3
17 {
18     // tag used to get our error handler from the context
19     struct error_handler_tag;
20 
21     template <typename Iterator>
22     class error_handler
23     {
24     public:
25 
26         typedef Iterator iterator_type;
27 
error_handler(Iterator first,Iterator last,std::ostream & err_out,std::string file="",int tabs=4)28         error_handler(
29             Iterator first, Iterator last, std::ostream& err_out
30           , std::string file = "", int tabs = 4)
31           : err_out(err_out)
32           , file(file)
33           , tabs(tabs)
34           , pos_cache(first, last) {}
35 
36         typedef void result_type;
37 
38         void operator()(Iterator err_pos, std::string const& error_message) const;
39         void operator()(Iterator err_first, Iterator err_last, std::string const& error_message) const;
operator ()(position_tagged pos,std::string const & message) const40         void operator()(position_tagged pos, std::string const& message) const
41         {
42             auto where = pos_cache.position_of(pos);
43             (*this)(where.begin(), where.end(), message);
44         }
45 
46         template <typename AST>
tag(AST & ast,Iterator first,Iterator last)47         void tag(AST& ast, Iterator first, Iterator last)
48         {
49             return pos_cache.annotate(ast, first, last);
50         }
51 
position_of(position_tagged pos) const52         boost::iterator_range<Iterator> position_of(position_tagged pos) const
53         {
54             return pos_cache.position_of(pos);
55         }
56 
get_position_cache() const57         position_cache<std::vector<Iterator>> const& get_position_cache() const
58         {
59             return pos_cache;
60         }
61 
62     private:
63 
64         void print_file_line(std::size_t line) const;
65         void print_line(Iterator line_start, Iterator last) const;
66         void print_indicator(Iterator& line_start, Iterator last, char ind) const;
67         void skip_whitespace(Iterator& err_pos, Iterator last) const;
68         void skip_non_whitespace(Iterator& err_pos, Iterator last) const;
69         Iterator get_line_start(Iterator first, Iterator pos) const;
70         std::size_t position(Iterator i) const;
71 
72         std::ostream& err_out;
73         std::string file;
74         int tabs;
75         position_cache<std::vector<Iterator>> pos_cache;
76     };
77 
78     template <typename Iterator>
print_file_line(std::size_t line) const79     void error_handler<Iterator>::print_file_line(std::size_t line) const
80     {
81         if (file != "")
82         {
83             err_out << "In file " << file << ", ";
84         }
85         else
86         {
87             err_out << "In ";
88         }
89 
90         err_out << "line " << line << ':' << std::endl;
91     }
92 
93     template <typename Iterator>
print_line(Iterator start,Iterator last) const94     void error_handler<Iterator>::print_line(Iterator start, Iterator last) const
95     {
96         auto end = start;
97         while (end != last)
98         {
99             auto c = *end;
100             if (c == '\r' || c == '\n')
101                 break;
102             else
103                 ++end;
104         }
105         typedef typename std::iterator_traits<Iterator>::value_type char_type;
106         std::basic_string<char_type> line{start, end};
107         err_out << x3::to_utf8(line) << std::endl;
108     }
109 
110     template <typename Iterator>
print_indicator(Iterator & start,Iterator last,char ind) const111     void error_handler<Iterator>::print_indicator(Iterator& start, Iterator last, char ind) const
112     {
113         for (; start != last; ++start)
114         {
115             auto c = *start;
116             if (c == '\r' || c == '\n')
117                 break;
118             else if (c == '\t')
119                 for (int i = 0; i < tabs; ++i)
120                     err_out << ind;
121             else
122                 err_out << ind;
123         }
124     }
125 
126     template <typename Iterator>
skip_whitespace(Iterator & err_pos,Iterator last) const127     void error_handler<Iterator>::skip_whitespace(Iterator& err_pos, Iterator last) const
128     {
129         // make sure err_pos does not point to white space
130         while (err_pos != last)
131         {
132             char c = *err_pos;
133             if (std::isspace(c))
134                 ++err_pos;
135             else
136                 break;
137         }
138     }
139 
140     template <typename Iterator>
skip_non_whitespace(Iterator & err_pos,Iterator last) const141     void error_handler<Iterator>::skip_non_whitespace(Iterator& err_pos, Iterator last) const
142     {
143         // make sure err_pos does not point to white space
144         while (err_pos != last)
145         {
146             char c = *err_pos;
147             if (std::isspace(c))
148                 break;
149             else
150                 ++err_pos;
151         }
152     }
153 
154     template <class Iterator>
get_line_start(Iterator first,Iterator pos) const155     inline Iterator error_handler<Iterator>::get_line_start(Iterator first, Iterator pos) const
156     {
157         Iterator latest = first;
158         for (Iterator i = first; i != pos; ++i)
159             if (*i == '\r' || *i == '\n')
160                 latest = i;
161         return latest;
162     }
163 
164     template <typename Iterator>
position(Iterator i) const165     std::size_t error_handler<Iterator>::position(Iterator i) const
166     {
167         std::size_t line { 1 };
168         typename std::iterator_traits<Iterator>::value_type prev { 0 };
169 
170         for (Iterator pos = pos_cache.first(); pos != i; ++pos) {
171             auto c = *pos;
172             switch (c) {
173             case '\n':
174                 if (prev != '\r') ++line;
175                 break;
176             case '\r':
177                 ++line;
178                 break;
179             default:
180                 break;
181             }
182             prev = c;
183         }
184 
185         return line;
186     }
187 
188     template <typename Iterator>
operator ()(Iterator err_pos,std::string const & error_message) const189     void error_handler<Iterator>::operator()(
190         Iterator err_pos, std::string const& error_message) const
191     {
192         Iterator first = pos_cache.first();
193         Iterator last = pos_cache.last();
194 
195         // make sure err_pos does not point to white space
196         skip_whitespace(err_pos, last);
197 
198         print_file_line(position(err_pos));
199         err_out << error_message << std::endl;
200 
201         Iterator start = get_line_start(first, err_pos);
202         if (start != first)
203             ++start;
204         print_line(start, last);
205         print_indicator(start, err_pos, '_');
206         err_out << "^_" << std::endl;
207     }
208 
209     template <typename Iterator>
operator ()(Iterator err_first,Iterator err_last,std::string const & error_message) const210     void error_handler<Iterator>::operator()(
211         Iterator err_first, Iterator err_last, std::string const& error_message) const
212     {
213         Iterator first = pos_cache.first();
214         Iterator last = pos_cache.last();
215 
216         // make sure err_pos does not point to white space
217         skip_whitespace(err_first, last);
218 
219         print_file_line(position(err_first));
220         err_out << error_message << std::endl;
221 
222         Iterator start = get_line_start(first, err_first);
223         if (start != first)
224             ++start;
225         print_line(start, last);
226         print_indicator(start, err_first, ' ');
227         print_indicator(start, err_last, '~');
228         err_out << " <<-- Here" << std::endl;
229     }
230 
231 }}}
232 
233 #endif
234