error.h 13.3 KB
Newer Older
1
2
#ifndef _SPEL_ERROR_H_
#define _SPEL_ERROR_H_
3
4
5

#include <string>
#include <iostream>
6
#include <exception>
7
8
9
#include <set>
#include <utility>
#include <sstream>
10
#include <thread>
Damien Leroux's avatar
Damien Leroux committed
11
#include <mutex>
12
#include <condition_variable>
13
#include <vector>
14
#include <deque>
15
16
extern "C" {
#include <unistd.h>
Damien Leroux's avatar
WIP.    
Damien Leroux committed
17
#include <sys/ioctl.h>
18
}
19
20
21

#define _WHITE "\x1b[37;1m"
#define _RED "\x1b[31;1m"
Damien Leroux's avatar
WIP.    
Damien Leroux committed
22
#define _GREEN "\x1b[32;1m"
23
24
25
26
#define _YELLOW "\x1b[33;1m"
#define _CYAN "\x1b[36;1m"
#define _NORMAL "\x1b[0;m"

27
28
#define MSG_HANDLER_IS_SYNCED

29
30
31
32
33
34
35
36
37
38
enum msg_channel { Out, Err, Log };

struct message_struc {
    msg_channel channel;
    std::string message;
};

typedef std::shared_ptr<message_struc> message_handle;

/*#define MAKE_MESSAGE(_dest_, _expr_) do { std::stringstream __s; __s << _expr_; _dest_ = __s.str(); } while (0)*/
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

static inline std::string __fetch_string(const std::ostream& os)
{
    return dynamic_cast<const std::stringstream*>(&os)->str();
}


static inline
std::ostream& operator << (std::ostream& os, const std::vector<double>& v)
{
    std::string sep = "";
    for (auto x: v) { os << sep << x; sep = " "; }
    return os;
}



#define MESSAGE(_expr_) __fetch_string(std::stringstream() << _expr_)
57
/*#define CREATE_MESSAGE(_var_, _channel_, _expr_) message_handle _var_(new message_struc {_channel_, MESSAGE(_expr_)});*/
Damien Leroux's avatar
Damien Leroux committed
58
59

#ifndef SPELL_UNSAFE_OUTPUT
60
#define CREATE_MESSAGE(_channel_, _what_) msg_handler_t::enqueue(message_handle{new message_struc {_channel_, _what_}});
Damien Leroux's avatar
Damien Leroux committed
61
62
63
64
65
66
67
68
69
70
71
72
73
#else
#define CREATE_MESSAGE(_channel_, _what_) switch(_channel_) { \
        case msg_channel::Out: \
            std::cout << _what_; \
            break; \
        case msg_channel::Err: \
            std::cerr << _what_; \
            break; \
        case msg_channel::Log: \
            std::clog << _what_; \
            break; \
    };
#endif
74
75


76
77
78
79
80
81
struct DirectOutputIsForbidden : public std::runtime_error {
    DirectOutputIsForbidden()
        : std::runtime_error("Direct output to std::cerr, cour, or clog is forbidden. Use CREATE_MESSAGE(channel, MESSAGE(expression)) instead.")
    {}
};

82
83

struct ostream_manager {
84
85
    /* FIXME indent/dedent must be managed by this very class... Maybe use \x1 for indent and \x2 for dedent... */

86
87
88
89
    /* inspired from http://stackoverflow.com/questions/22042414/c-stream-insert-string-after-newline */
    class HeaderInserter : public std::streambuf {
        std::streambuf* dest;
        bool start_of_line;
90
91
92
93
        /*int indent;*/
        std::vector<std::string> prefix;
        bool prefix_insert_mode;
        std::stringstream prefix_ss;
94
95
96
97
98
99
100
    protected:
        int overflow(int ch) override;

    public:
        HeaderInserter(std::streambuf* dest)
            : dest(dest)
            , start_of_line(true)
101
102
103
104
            /*, indent(0)*/
            , prefix()
            , prefix_insert_mode(false)
            , prefix_ss()
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
        {}
    };

    class ForbidOutput : public std::streambuf {
    protected:
        int overflow(int) override
        {
            /* Direct output is forbidden */
            /*throw std::ios_base::failure("Direct output is forbidden");*/
            abort();
        }
    };

    std::streambuf* old_cout_rdbuf;
    std::streambuf* old_clog_rdbuf;
    std::streambuf* old_cerr_rdbuf;
    HeaderInserter hi;
    ForbidOutput forbid;
    std::ostream cerr, cout, clog;

    ostream_manager()
        : old_cout_rdbuf(std::cout.rdbuf())
        , old_clog_rdbuf(std::clog.rdbuf())
        , old_cerr_rdbuf(std::cerr.rdbuf())
        , hi(old_cout_rdbuf)
        , forbid()
        , cerr(old_cerr_rdbuf), cout(old_cout_rdbuf), clog(&hi)
    {
133
#ifndef SPELL_UNSAFE_OUTPUT
134
135
136
        std::clog.rdbuf(&forbid);
        std::cout.rdbuf(&forbid);
        std::cerr.rdbuf(&forbid);
137
#endif
138
139
140
141
    }

    ~ostream_manager()
    {
142
#ifndef SPELL_UNSAFE_OUTPUT
143
144
145
        std::clog.rdbuf(old_clog_rdbuf);
        std::cout.rdbuf(old_cout_rdbuf);
        std::cerr.rdbuf(old_cerr_rdbuf);
146
#endif
147
148
149
150
151
152
153
154
155
    }
};

struct message_queue : public ostream_manager {
    typedef std::mutex mutex_type;
    typedef std::unique_lock<mutex_type> scoped_lock_type;

    std::deque<message_handle> m_queue;
    mutex_type m_mutex;
156
    mutex_type m_stream_mutex;
157
    std::condition_variable m_condition;
Damien Leroux's avatar
Damien Leroux committed
158
    std::condition_variable m_flush_condition;
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190

    bool m_stop;

    std::thread m_thread;

    message_queue()
        : ostream_manager()
        , m_queue()
        , m_mutex()
        , m_condition()
        , m_stop(false)
        , m_thread([this] () { run(); })
    {
    }

    ~message_queue()
    {
        {
            scoped_lock_type lock(m_mutex);
            m_stop = true;
        }
        m_condition.notify_one();
        m_thread.join();
    }

    void enqueue(const message_handle& mh)
    {
        scoped_lock_type slt(m_mutex);
        m_queue.push_back(mh);
        m_condition.notify_one();
    }

191
192
    void lock_stream() { m_stream_mutex.lock(); }
    void unlock_stream() { m_stream_mutex.unlock(); }
193

194
    void run();
Damien Leroux's avatar
Damien Leroux committed
195
196
197
198
199
200
201
202
203

    void wait_for_flush()
    {
        scoped_lock_type lock(m_mutex);
        while (!m_queue.empty()) {
            m_flush_condition.wait(lock);
        }

    }
204
205
206
};


207
struct msg_handler_t {
208
#ifdef MSG_HANDLER_IS_SYNCED
209
210
    typedef std::recursive_mutex lock_type;
    typedef std::unique_lock<lock_type> scoped_lock_type;
211
212
213
214
215
216
217
218
219
#else
    typedef struct {
        void lock() {}
        void unlock() {}
    } lock_type;
    typedef struct _slt {
        _slt(lock_type&) {}
    } scoped_lock_type;
#endif
220

221
222
223
224
    struct state_t {
        bool color;
        std::set<std::string> workarounds;
        int count;
225
        int debug_indent;
226
        std::vector<std::function<void()>> hooks;
227
        message_queue queue;
228
229
230
231
232
233

        const char* error() { ++count; return color ? _RED : ""; }
        const char* warning() { return color ? _YELLOW : ""; }
        const char* info() { return color ? _CYAN : ""; }
        const char* normal() { return color ? _NORMAL : ""; }

234
235
        state_t()
            : color(!!isatty(fileno(stdout))), workarounds(), count(0), debug_indent(0), hooks()
236
            , queue()
237
        {
238
            /*std::cout << "Message handler instance created." << std::endl;*/
239
        }
240
241
		~state_t()
        {}
242
243
        void check(bool fatal);
        void reset();
244
        void run_hooks() { for (auto& f: hooks) { f(); } }
245
246
247
248
249
250
251
252
253
254
255
256
257
258
    };

    static state_t& instance() { static state_t _; return _; }

    static void set_color(bool _) { instance().color = _; }
    static bool color() { return instance().color; }

    static const char* e() { return instance().error(); }
    static const char* w() { return instance().warning(); }
    static const char* i() { return instance().info(); }
    static const char* n() { return instance().normal(); }

    static void check(bool fatal) { instance().check(fatal); }
    static void reset() { instance().reset(); }
259

260
261
262
    static void hook(std::function<void()>&& f) { instance().hooks.push_back(f); }
    static void run_hooks() { instance().run_hooks(); }

263
264
265
266
    static void indent() { instance().debug_indent += 3; }
    static void dedent() { instance().debug_indent -= 3; }
    static int get_indent() { return instance().debug_indent; }

267
    static void enqueue(const message_handle& mh) { instance().queue.enqueue(mh); }
Damien Leroux's avatar
Damien Leroux committed
268
    static void wait_for_flush() { instance().queue.wait_for_flush(); }
269

Damien Leroux's avatar
WIP.    
Damien Leroux committed
270
271
272
273
274
275
276
    static int termcols()
    {
        struct winsize w;
        ioctl(0, TIOCGWINSZ, &w);
        return w.ws_col;
    }

277
    static lock_type mutex;
278
279
};

280
281
282
283
inline
int ostream_manager::HeaderInserter::overflow(int ch)
{
    int retval = 0;
284
    if (ch == 1) {
285
286
287
288
289
290
291
292
        /*indent += 3;*/
        prefix_insert_mode = !prefix_insert_mode;
        if (prefix_insert_mode) {
            prefix_ss.clear();
            prefix_ss.str(std::string());
        } else {
            prefix.push_back(prefix_ss.str());
        }
293
    } else if (ch == 2) {
294
295
296
297
298
299
300
301
302
        if (prefix_insert_mode) {
            overflow('\x1');
        }
        if (prefix.size()) {
            prefix.pop_back();
        }
        /*indent -= 3 * (indent > 0);*/
    } else if (prefix_insert_mode) {
        prefix_ss << (char) ch;
303
    } else if (ch != traits_type::eof()) {
304
        if (start_of_line) {
305
306
307
308
            for (const std::string& s: prefix) {
                for (const char c: s) {
                    dest->sputc(c);
                }
309
            }
310
311
312
            /*for (int i = 0; i < indent; ++i) {*/
                /*dest->sputc(' ');*/
            /*}*/
313
314
315
316
317
318
319
        }
        retval = dest->sputc( ch );
        start_of_line = ch == '\n';
    }
    return retval;
}

320
321
322
inline
void message_queue::run()
{
Damien Leroux's avatar
Damien Leroux committed
323
    message_handle  next;
324
325
    while (true)
    {
Damien Leroux's avatar
Damien Leroux committed
326
327
        {
            scoped_lock_type lock(m_mutex);
328

Damien Leroux's avatar
Damien Leroux committed
329
            m_condition.wait(lock, [this]() { return m_stop || !m_queue.empty(); });
330

Damien Leroux's avatar
Damien Leroux committed
331
332
333
            /*while (!m_stop && m_queue.empty()) {*/
                /*m_condition.wait(lock);*/
            /*}*/
334

Damien Leroux's avatar
Damien Leroux committed
335
336
337
            if (m_stop && m_queue.empty()) {
                return;
            }
338

Damien Leroux's avatar
Damien Leroux committed
339
340
            next = m_queue.front();
            m_queue.pop_front();
341

Damien Leroux's avatar
Damien Leroux committed
342
343
344
345
346
        }
        if (next->message == "\x3") {
            m_flush_condition.notify_one();
            continue;
        } else if (next->message.size() == 0) {
347
348
            continue;
        }
Damien Leroux's avatar
Damien Leroux committed
349
350
351
        std::ostream& channel = next->channel == msg_channel::Out ? cout
                              : next->channel == msg_channel::Log ? clog
                              : cerr;
352
353
354
355
356
357
358
        lock_stream();
        channel << next->message << std::flush;
        unlock_stream();
        msg_handler_t::run_hooks();
    }
}

359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
#define MSG_ERROR(_msg_expr_, _workaround_expr_) \
    do {\
        CREATE_MESSAGE(msg_channel::Err, MESSAGE(msg_handler_t::e() << "[ERR] " << _msg_expr_ << msg_handler_t::n() << std::endl));\
        std::stringstream s; s << _workaround_expr_;\
        if (s.str().size()) { msg_handler_t::instance().workarounds.insert(s.str()); }\
} while (0)

#define MSG_WARNING(_msg_expr_) \
    do {\
        CREATE_MESSAGE(msg_channel::Out, MESSAGE(msg_handler_t::w() << "[WRN] " << _msg_expr_ << msg_handler_t::n() << std::endl));\
} while (0)

#define MSG_INFO(_msg_expr_) \
    do {\
        CREATE_MESSAGE(msg_channel::Out, MESSAGE(msg_handler_t::i() << "[MSG] " << _msg_expr_ << msg_handler_t::n() << std::endl));\
} while (0)

#define MSG_DEBUG(_msg_expr_) \
    do {\
        CREATE_MESSAGE(msg_channel::Log, MESSAGE(_msg_expr_ << std::endl));\
} while (0)
380

Damien Leroux's avatar
Damien Leroux committed
381
382
383
384
385
#define MSG_QUEUE_FLUSH() do { \
    CREATE_MESSAGE(msg_channel::Log, "\x3"); \
    msg_handler_t::wait_for_flush(); \
} while (0)

386
#if 0
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
#define MSG_ERROR(_msg_expr_, _workaround_expr_) \
    do {\
        {msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);\
        std::cerr << msg_handler_t::e() << "[ERR] " << _msg_expr_ << msg_handler_t::n() << std::endl;}\
        std::stringstream s; s << _workaround_expr_;\
        if (s.str().size()) { msg_handler_t::instance().workarounds.insert(s.str()); }\
        msg_handler_t::run_hooks();\
} while (0)

#define MSG_WARNING(_msg_expr_) \
    do {\
        msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);\
        std::cerr << msg_handler_t::w() << "[WRN] " << _msg_expr_ << msg_handler_t::n() << std::endl;\
        msg_handler_t::run_hooks();\
    } while(0)

#define MSG_INFO(_msg_expr_) \
    do {\
        msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);\
        std::cout << msg_handler_t::i() << "[MSG] " << _msg_expr_ << msg_handler_t::n() << std::endl;\
        msg_handler_t::run_hooks();\
    } while(0)

#define MSG_DEBUG(_msg_expr_) \
    do {\
        msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);\
413
        std::clog << _msg_expr_ << std::endl;\
414
415
        msg_handler_t::run_hooks();\
    } while(0)
416
#endif
417

418
419
#define MSG_DEBUG_INDENT CREATE_MESSAGE(msg_channel::Log, "\x1   \x1")
#define MSG_DEBUG_INDENT_EXPR(_str_) CREATE_MESSAGE(msg_channel::Log, MESSAGE('\x1' << _str_ << '\x1'))
420
#define MSG_DEBUG_DEDENT CREATE_MESSAGE(msg_channel::Log, "\x2")
421

422
423
424
425
426
427
428

struct scoped_indent {
    scoped_indent() { MSG_DEBUG_INDENT; }
    scoped_indent(const std::string& str) { MSG_DEBUG_INDENT_EXPR(str); }
    ~scoped_indent() { MSG_DEBUG_DEDENT; }
};

429

430
431
inline void msg_handler_t::state_t::check(bool fatal)
{
432
    msg_handler_t::scoped_lock_type _(msg_handler_t::mutex);
433
    if (count > 0) {
434
435
436
437
        CREATE_MESSAGE(msg_channel::Err, MESSAGE(
                info() << "[MSG] " << count << " error"
                << (count > 1 ? "s were" : " was")
                << " reported. Suggestions to fix this:" << std::endl));
438
        for (auto& w: workarounds) {
439
            CREATE_MESSAGE(msg_channel::Err, MESSAGE(info() << "      - " << w << normal() << std::endl));
440
441
        }
        if (fatal) {
442
            CREATE_MESSAGE(msg_channel::Out, MESSAGE(normal() <<"At least one fatal error encountered. Aborting process." << std::endl));
443
444
445
446
447
448
449
450
451
452
            exit(-count);
        } else {
            reset();
        }
    }
}

inline void msg_handler_t::state_t::reset()
{
    if (workarounds.size()) {
453
        MSG_WARNING(workarounds.size() << " workarounds silently discarded");
454
455
456
457
458
459
460
    }
    count = 0;
    workarounds.clear();
}

#define WHITE (msg_handler_t::instance().color ? _WHITE : "")
#define YELLOW (msg_handler_t::instance().color ? _YELLOW : "")
461
#define GREEN (msg_handler_t::instance().color ? _GREEN : "")
462
463
464
465
#define RED (msg_handler_t::instance().color ? _RED : "")
#define CYAN (msg_handler_t::instance().color ? _CYAN : "")
#define NORMAL (msg_handler_t::instance().color ? _NORMAL : "")

466
467
468
469
470
471
#ifdef NDEBUG
#define DUMP_FILE_LINE()
#else
#define DUMP_FILE_LINE() MSG_DEBUG(__FILE__ << ':' << __LINE__)
#endif

472
473
#endif