cppast/test/test_parser.hpp
2022-02-07 20:43:22 +01:00

263 lines
7.8 KiB
C++

// Copyright (C) 2017-2022 Jonathan Müller and cppast contributors
// SPDX-License-Identifier: MIT
#ifndef CPPAST_TEST_PARSER_HPP_INCLUDED
#define CPPAST_TEST_PARSER_HPP_INCLUDED
#include <fstream>
#include <catch2/catch.hpp>
#include <cppast/code_generator.hpp>
#include <cppast/cpp_class.hpp>
#include <cppast/cpp_entity_kind.hpp>
#include <cppast/cpp_expression.hpp>
#include <cppast/cpp_type.hpp>
#include <cppast/libclang_parser.hpp>
#include <cppast/visitor.hpp>
inline void write_file(const char* name, const char* code)
{
std::ofstream file(name);
file << code;
}
inline std::unique_ptr<cppast::cpp_file> parse_file(const cppast::cpp_entity_index& idx,
const char* name,
bool fast_preprocessing = false,
cppast::cpp_standard standard
= cppast::cpp_standard::cpp_latest)
{
using namespace cppast;
// Creating a config is expensive, so we remember a default one.
static auto default_config = libclang_compile_config();
auto config = default_config;
config.set_flags(standard);
config.fast_preprocessing(fast_preprocessing);
libclang_parser p(default_logger());
std::unique_ptr<cppast::cpp_file> result;
REQUIRE_NOTHROW(result = p.parse(idx, name, config));
REQUIRE(!p.error());
return result;
}
inline std::unique_ptr<cppast::cpp_file> parse(const cppast::cpp_entity_index& idx,
const char* name, const char* code,
bool fast_preprocessing = false,
cppast::cpp_standard standard
= cppast::cpp_standard::cpp_latest)
{
write_file(name, code);
return parse_file(idx, name, fast_preprocessing, standard);
}
class test_generator : public cppast::code_generator
{
public:
test_generator(generation_options options) : options_(std::move(options)) {}
const std::string& str() const noexcept
{
return str_;
}
private:
generation_options do_get_options(const cppast::cpp_entity&,
cppast::cpp_access_specifier_kind) override
{
return options_;
}
void do_indent() override
{
++indent_;
}
void do_unindent() override
{
if (indent_)
--indent_;
}
void do_write_token_seq(cppast::string_view tokens) override
{
if (was_newline_)
{
str_ += std::string(indent_ * 2u, ' ');
was_newline_ = false;
}
str_ += tokens.c_str();
}
void do_write_newline() override
{
str_ += "\n";
was_newline_ = true;
}
std::string str_;
generation_options options_;
unsigned indent_ = 0;
bool was_newline_ = false;
};
inline std::string get_code(const cppast::cpp_entity& e,
cppast::code_generator::generation_options options = {})
{
test_generator generator(options);
cppast::generate_code(generator, e);
auto str = generator.str();
if (!str.empty() && str.back() == '\n')
str.pop_back();
return str;
}
template <typename Func, typename T>
auto visit_callback(bool, Func f, const T& t) -> decltype(f(t) == true)
{
return f(t);
}
template <typename Func, typename T>
bool visit_callback(int check, Func f, const T& t)
{
f(t);
return check == 1;
}
template <typename T, typename Func>
unsigned test_visit(const cppast::cpp_file& file, Func f, bool check_code = true)
{
auto count = 0u;
cppast::visit(file, [&](const cppast::cpp_entity& e, cppast::visitor_info info) {
if (info.event == cppast::visitor_info::container_entity_exit)
return true; // already handled
if (e.kind() == T::kind())
{
auto& obj = static_cast<const T&>(e);
auto check_cur = visit_callback(check_code, f, obj);
++count;
if (check_cur)
{
INFO(e.name());
REQUIRE(e.comment());
REQUIRE(e.comment().value() == get_code(e));
}
}
return true;
});
return count;
}
// number of direct children
template <class Entity>
unsigned count_children(const Entity& cont)
{
return unsigned(std::distance(cont.begin(), cont.end()));
}
// ignores templated scopes
inline std::string full_name(const cppast::cpp_entity& e)
{
if (e.name().empty())
return "";
else if (cppast::is_parameter(e.kind()))
// parameters don't have a full name
return e.name();
std::string scopes;
for (auto cur = e.parent(); cur; cur = cur.value().parent())
// prepend each scope, if there is any
type_safe::with(cur.value().scope_name(), [&](const cppast::cpp_scope_name& cur_scope) {
scopes = cur_scope.name() + "::" + scopes;
});
if (e.kind() == cppast::cpp_entity_kind::class_t)
{
auto& c = static_cast<const cppast::cpp_class&>(e);
return scopes + c.semantic_scope() + c.name();
}
else
return scopes + e.name();
}
// checks the full name/parent
inline void check_parent(const cppast::cpp_entity& e, const char* parent_name,
const char* full_name)
{
REQUIRE(e.parent());
REQUIRE(e.parent().value().name() == parent_name);
REQUIRE(::full_name(e) == full_name);
}
bool equal_types(const cppast::cpp_entity_index& idx, const cppast::cpp_type& parsed,
const cppast::cpp_type& synthesized);
inline bool equal_expressions(const cppast::cpp_expression& parsed,
const cppast::cpp_expression& synthesized)
{
using namespace cppast;
if (parsed.kind() != synthesized.kind())
return false;
switch (parsed.kind())
{
case cpp_expression_kind::unexposed_t:
return static_cast<const cpp_unexposed_expression&>(parsed).expression().as_string()
== static_cast<const cpp_unexposed_expression&>(synthesized)
.expression()
.as_string();
case cpp_expression_kind::literal_t:
return static_cast<const cpp_literal_expression&>(parsed).value()
== static_cast<const cpp_literal_expression&>(synthesized).value();
}
return false;
}
template <typename T, class Predicate>
bool equal_ref(const cppast::cpp_entity_index& idx,
const cppast::basic_cpp_entity_ref<T, Predicate>& parsed,
const cppast::basic_cpp_entity_ref<T, Predicate>& synthesized,
const char* full_name_override = nullptr)
{
if (parsed.name() != synthesized.name())
return false;
else if (parsed.is_overloaded() != synthesized.is_overloaded())
return false;
else if (parsed.is_overloaded())
return false;
auto entities = parsed.get(idx);
if (entities.size() != 1u)
return false;
return entities[0u]->name().empty()
|| full_name(*entities[0u]) == (full_name_override ? full_name_override : parsed.name());
}
template <typename T>
void check_template_parameters(
const T& templ, std::initializer_list<std::pair<cppast::cpp_entity_kind, const char*>> params)
{
// no need to check more
auto cur = params.begin();
for (auto& param : templ.parameters())
{
REQUIRE(cur != params.end());
REQUIRE(param.kind() == cur->first);
REQUIRE(param.name() == cur->second);
++cur;
}
REQUIRE(cur == params.end());
}
#endif // CPPAST_TEST_PARSER_HPP_INCLUDED