/*
Ousía
Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include
#include
#include "Logger.hpp"
#include "Terminal.hpp"
namespace ousia {
/* Class Logger */
void Logger::log(Severity severity, const std::string &msg,
const SourceLocation &loc)
{
// Assemble the message and pass it through the filter, then process it
Message message { severity, std::move(msg), loc };
if (filterMessage(message)) {
processMessage(message);
}
}
LoggerFork Logger::fork() { return LoggerFork(this); }
/* Class LoggerFork */
void LoggerFork::processMessage(const Message &msg)
{
calls.push_back(Call(CallType::MESSAGE, messages.size()));
messages.push_back(msg);
}
void LoggerFork::processPushFile(const File &file)
{
calls.push_back(Call(CallType::PUSH_FILE, files.size()));
files.push_back(file);
}
void LoggerFork::processPopFile()
{
calls.push_back(Call(CallType::POP_FILE, 0));
}
void LoggerFork::processSetDefaultLocation(const SourceLocation &loc)
{
// Check whether setDefaultLocation was called immediately before, if yes,
// simply override the data
if (!calls.empty() && calls.back().type == CallType::SET_DEFAULT_LOCATION) {
locations.back() = loc;
} else {
calls.push_back(Call(CallType::SET_DEFAULT_LOCATION, locations.size()));
locations.push_back(loc);
}
}
void LoggerFork::purge()
{
calls.clear();
messages.clear();
files.clear();
locations.clear();
}
void LoggerFork::commit()
{
for (const Call &call : calls) {
switch (call.type) {
case CallType::MESSAGE: {
if (parent->filterMessage(messages[call.dataIdx])) {
parent->processMessage(messages[call.dataIdx]);
}
break;
}
case CallType::PUSH_FILE: {
parent->processPushFile(files[call.dataIdx]);
break;
}
case CallType::POP_FILE:
parent->processPopFile();
break;
case CallType::SET_DEFAULT_LOCATION:
parent->processSetDefaultLocation(locations[call.dataIdx]);
break;
}
}
purge();
}
/* Class ConcreteLogger */
static const Logger::File EMPTY_FILE{"", SourceLocation{}, nullptr, nullptr};
void ConcreteLogger::processPushFile(const File &file)
{
files.push_back(file);
}
void ConcreteLogger::processPopFile() { files.pop_back(); }
bool ConcreteLogger::filterMessage(const Message &msg)
{
// Increment the message count for this severity
uint8_t sev = static_cast(msg.severity);
if (sev >= messageCounts.size()) {
messageCounts.resize(sev + 1);
}
messageCounts[sev]++;
// Filter messages with too small severity
return sev >= static_cast(minSeverity);
}
void ConcreteLogger::processSetDefaultLocation(const SourceLocation &loc)
{
defaultLocation = loc;
}
const Logger::File &ConcreteLogger::currentFile() const
{
if (!files.empty()) {
return files.back();
}
return EMPTY_FILE;
}
const std::string &ConcreteLogger::currentFilename() const
{
return currentFile().file;
}
const SourceLocation &ConcreteLogger::messageLocation(const Message &msg) const
{
if (msg.loc.valid()) {
return msg.loc;
}
return defaultLocation;
}
SourceContext ConcreteLogger::messageContext(const Message &msg) const
{
const Logger::File &file = currentFile();
const SourceLocation &loc = messageLocation(msg);
if (file.ctxCallback && loc.valid()) {
return file.ctxCallback(loc, file.ctxCallbackData);
}
return SourceContext{};
}
Severity ConcreteLogger::getMaxEncounteredSeverity()
{
for (ssize_t i = messageCounts.size() - 1; i >= 0; i--) {
if (messageCounts[i] > 0) {
return static_cast(i);
}
}
return Severity::DEBUG;
}
size_t ConcreteLogger::getSeverityCount(Severity severity)
{
uint8_t sev = static_cast(severity);
if (sev >= messageCounts.size()) {
return 0;
}
return messageCounts[sev];
}
void ConcreteLogger::reset()
{
files.clear();
messageCounts.clear();
}
bool ConcreteLogger::hasError()
{
return getSeverityCount(Severity::ERROR) > 0 ||
getSeverityCount(Severity::FATAL_ERROR) > 0;
}
/* Class TerminalLogger */
void TerminalLogger::processMessage(const Message &msg)
{
Terminal t(useColor);
// Fetch filename, position and context
const std::string filename = currentFilename();
const SourceLocation pos = messageLocation(msg);
const SourceContext ctx = messageContext(msg);
// Print the file name
bool hasFile = !filename.empty();
if (hasFile) {
os << t.bright() << filename << t.reset();
}
// Print line and column number
if (pos.hasLine()) {
if (hasFile) {
os << ':';
}
os << t.bright() << pos.line << t.reset();
if (pos.hasColumn()) {
os << ':' << pos.column;
}
}
// Print the optional seperator
if (hasFile || pos.hasLine()) {
os << ": ";
}
// Print the severity
switch (msg.severity) {
case Severity::DEBUG:
break;
case Severity::NOTE:
os << t.color(Terminal::CYAN, true) << "note: ";
break;
case Severity::WARNING:
os << t.color(Terminal::MAGENTA, true) << "warning: ";
break;
case Severity::ERROR:
os << t.color(Terminal::RED, true) << "error: ";
break;
case Severity::FATAL_ERROR:
os << t.color(Terminal::RED, true) << "fatal error: ";
break;
}
os << t.reset();
// Print the actual message
os << msg.msg << std::endl;
// Print the error message context if available
if (ctx.valid()) {
size_t relPos = ctx.relPos;
if (ctx.truncatedStart) {
os << "[...] ";
}
os << ctx.text;
if (ctx.truncatedEnd) {
os << " [...]";
}
os << std::endl;
if (ctx.truncatedStart) {
os << " ";
}
for (size_t i = 0; i < relPos; i++) {
if (i < ctx.text.size() && ctx.text[i] == '\t') {
os << '\t';
} else {
os << ' ';
}
}
os << t.color(Terminal::GREEN) << '^' << t.reset() << std::endl;
}
}
}