From 108fd1b2eeb35b5107f3c8ec1f7c7102b208f45b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20M=C3=BCller?= Date: Thu, 16 Feb 2017 22:08:56 +0100 Subject: [PATCH] Add diagnostic_logger --- include/cppast/diagnostic.hpp | 80 ++++++++++++++++++++++++++++++ include/cppast/libclang_parser.hpp | 2 +- include/cppast/parser.hpp | 46 ++++++++++++++++- src/libclang/libclang_parser.cpp | 5 +- src/libclang/preprocessor.cpp | 23 +++++---- src/libclang/preprocessor.hpp | 3 +- src/parser.cpp | 18 +++++++ test/test_parser.hpp | 3 +- 8 files changed, 165 insertions(+), 15 deletions(-) create mode 100644 include/cppast/diagnostic.hpp create mode 100644 src/parser.cpp diff --git a/include/cppast/diagnostic.hpp b/include/cppast/diagnostic.hpp new file mode 100644 index 0000000..b6348b8 --- /dev/null +++ b/include/cppast/diagnostic.hpp @@ -0,0 +1,80 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#ifndef CPPAST_DIAGNOSTIC_HPP_INCLUDED +#define CPPAST_DIAGNOSTIC_HPP_INCLUDED + +#include + +#include + +namespace cppast +{ + /// Describes a physical source location attached to a [cppast::diagnostic](). + /// \notes All information might be unavailable. + struct source_location + { + type_safe::optional entity; + type_safe::optional file; + type_safe::optional line; + + /// \returns A source location where all information is available. + static source_location make(std::string entity, std::string file, unsigned line) + { + return {std::move(entity), std::move(file), line}; + } + + /// \returns A source location where only file and line information is available. + static source_location make(std::string file, unsigned line) + { + return {type_safe::nullopt, std::move(file), line}; + } + + /// \returns A source location where only the entity name is available. + static source_location make(std::string entity) + { + return {std::move(entity), type_safe::nullopt, type_safe::nullopt}; + } + + /// \returns A possible string representation of the source location. + /// \notes It will include a separator, but no trailing whitespace. + std::string to_string() const + { + std::string result; + if (file) + { + result += file.value() + ":"; + if (line) + result += line.value() + ":"; + if (entity) + result += " (" + entity.value() + "):"; + } + else if (entity) + result += entity.value() + ":"; + + return result; + } + }; + + /// The severity of a [cppast::diagnostic](). + enum class severity + { + warning, //< A warning that doesn't impact AST generation. + error, //< A non-critical error that does impact AST generation but not critically. + critical, //< A critical error where AST generation isn't possible. + /// \notes This will usually result in an exception being thrown after the diagnostic has been displayed. + }; + + /// A diagnostic. + /// + /// It represents an error message from a [cppast::parser](). + struct diagnostic + { + std::string message; + source_location location; + cppast::severity severity; + }; +} // namespace cppast + +#endif // CPPAST_DIAGNOSTIC_HPP_INCLUDED diff --git a/include/cppast/libclang_parser.hpp b/include/cppast/libclang_parser.hpp index 9f22621..cc86455 100644 --- a/include/cppast/libclang_parser.hpp +++ b/include/cppast/libclang_parser.hpp @@ -69,7 +69,7 @@ namespace cppast class libclang_parser final : public parser { public: - libclang_parser(); + explicit libclang_parser(type_safe::object_ref logger); ~libclang_parser() noexcept override; private: diff --git a/include/cppast/parser.hpp b/include/cppast/parser.hpp index 68b6902..92b9498 100644 --- a/include/cppast/parser.hpp +++ b/include/cppast/parser.hpp @@ -11,6 +11,39 @@ namespace cppast { class cpp_entity_index; + class diagnostic; + + /// Base class for a [cppast::diagnostic]() logger. + /// + /// Its task is controlling how diagnostic are being displayed. + class diagnostic_logger + { + public: + diagnostic_logger() noexcept = default; + diagnostic_logger(const diagnostic_logger&) = delete; + diagnostic_logger& operator=(const diagnostic_logger&) = delete; + virtual ~diagnostic_logger() noexcept = default; + + /// \effects Logs the diagnostic by invoking the `do_log()` member function. + /// \returns Whether or not the diagnostic was logged. + /// \notes `source` points to a string literal that gives additional context to what generates the message. + bool log(const char* source, const diagnostic& d) const + { + return do_log(source, d); + } + + private: + virtual bool do_log(const char* source, const diagnostic& d) const = 0; + }; + + /// A [cppast::diagnostic_logger]() that logs to `stderr`. + /// + /// It prints all diagnostics in an implementation-defined format. + class stderr_diagnostic_logger final : public diagnostic_logger + { + private: + bool do_log(const char* source, const diagnostic& d) const override; + }; /// Base class for a parser. /// @@ -33,13 +66,24 @@ namespace cppast } protected: - parser() = default; + /// \effects Creates it giving it a reference to the logger it uses. + explicit parser(type_safe::object_ref logger) : logger_(logger) + { + } + + /// \returns A reference to the logger used. + const diagnostic_logger& logger() const noexcept + { + return *logger_; + } private: /// \effects Parses the given file. /// \returns The [cppast::cpp_file]() object describing it. virtual std::unique_ptr do_parse(const cpp_entity_index& idx, std::string path, const compile_config& config) const = 0; + + type_safe::object_ref logger_; }; } // namespace cppast diff --git a/src/libclang/libclang_parser.cpp b/src/libclang/libclang_parser.cpp index 47012e9..83697b7 100644 --- a/src/libclang/libclang_parser.cpp +++ b/src/libclang/libclang_parser.cpp @@ -95,7 +95,8 @@ struct libclang_parser::impl } }; -libclang_parser::libclang_parser() : pimpl_(new impl) +libclang_parser::libclang_parser(type_safe::object_ref logger) +: parser(logger), pimpl_(new impl) { } @@ -164,7 +165,7 @@ std::unique_ptr libclang_parser::do_parse(const cpp_entity_index& idx, auto& config = static_cast(c); // preprocess + parse - auto preprocessed = detail::preprocess(config, path.c_str()); + auto preprocessed = detail::preprocess(config, path.c_str(), logger()); auto tu = get_cxunit(pimpl_->index, config, path.c_str(), preprocessed.source); // convert entity hierachies diff --git a/src/libclang/preprocessor.cpp b/src/libclang/preprocessor.cpp index 6421bad..e6c0a8b 100644 --- a/src/libclang/preprocessor.cpp +++ b/src/libclang/preprocessor.cpp @@ -7,7 +7,6 @@ #include #include #include -#include // treat the tiny-process-library as header only #include @@ -23,6 +22,7 @@ #include #include +#include using namespace cppast; namespace ts = type_safe; @@ -54,9 +54,10 @@ namespace } // gets the full preprocessor output - std::string get_full_preprocess_output(const libclang_compile_config& c, const char* full_path) + std::string get_full_preprocess_output(const libclang_compile_config& c, const char* full_path, + const diagnostic_logger& logger) { - std::string preprocessed; + std::string preprocessed, diagnostics; auto cmd = get_command(c, full_path); Process process(cmd, "", @@ -66,16 +67,20 @@ namespace if (*str != '\r') preprocessed.push_back(*str); }, - [&](const char* str, std::size_t n) { - std::fprintf(stderr, "%.*s\n", static_cast(n), - str); // TODO: log error properly - }); + [&](const char* str, std::size_t n) { diagnostics.append(str, n); }); auto exit_code = process.get_exit_status(); if (exit_code != 0) + { + if (!diagnostics.empty()) + logger.log("preprocessor", + diagnostic{std::move(diagnostics), {}, severity::critical}); throw libclang_error("preprocessor: command '" + cmd + "' exited with non-zero exit code (" + std::to_string(exit_code) + ")"); + } + else if (!diagnostics.empty()) + logger.log("preprocessor", diagnostic{std::move(diagnostics), {}, severity::error}); return preprocessed; } @@ -369,11 +374,11 @@ namespace } detail::preprocessor_output detail::preprocess(const libclang_compile_config& config, - const char* path) + const char* path, const diagnostic_logger& logger) { detail::preprocessor_output result; - auto output = get_full_preprocess_output(config, path); + auto output = get_full_preprocess_output(config, path, logger); position p(ts::ref(result.source), output.c_str()); std::size_t file_depth = 0u; diff --git a/src/libclang/preprocessor.hpp b/src/libclang/preprocessor.hpp index 8bc268c..4ceb168 100644 --- a/src/libclang/preprocessor.hpp +++ b/src/libclang/preprocessor.hpp @@ -24,7 +24,8 @@ namespace cppast std::vector entities; }; - preprocessor_output preprocess(const libclang_compile_config& config, const char* path); + preprocessor_output preprocess(const libclang_compile_config& config, const char* path, + const diagnostic_logger& logger); } } // namespace cppast::detail diff --git a/src/parser.cpp b/src/parser.cpp new file mode 100644 index 0000000..f632576 --- /dev/null +++ b/src/parser.cpp @@ -0,0 +1,18 @@ +// Copyright (C) 2017 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#include + +#include +#include + +#include + +using namespace cppast; + +bool stderr_diagnostic_logger::do_log(const char* source, const diagnostic& d) const +{ + std::fprintf(stderr, "[%s] %s %s", source, d.location.to_string().c_str(), d.message.c_str()); + return true; +} diff --git a/test/test_parser.hpp b/test/test_parser.hpp index 34fbcc7..b616266 100644 --- a/test/test_parser.hpp +++ b/test/test_parser.hpp @@ -29,7 +29,8 @@ std::unique_ptr parse(const cppast::cpp_entity_index& idx, con libclang_compile_config config; config.set_flags(cpp_standard::cpp_latest); - libclang_parser p; + static stderr_diagnostic_logger logger; + libclang_parser p(type_safe::ref(logger)); return p.parse(idx, name, config); }