2
0
mirror of https://github.com/boostorg/log.git synced 2026-02-10 23:42:22 +00:00
Files
log/src/named_scope_format_parser.cpp
Andrey Semashev a31ec74e51 Make sure that Windows API version is consistent for all sources.
Include winapi/config.hpp as soon as possible; avoid including even
Boost.Config before it so that it doesn't set Windows API version
accidentally by including some third party header. In all source files,
include detail/config.hpp or detail/setup_config.hpp first thing.

Moved all WinAPI config macros to the Jamfile.v2 and removed
windows_version.hpp as it was no longer needed. Also enabled inclusion of
windows.h while compiling the library.

Removed auto-linking with psapi.lib and advapi32.lib as it was no longer
working after ecf3114. Added linking with advapi32.lib to Jamfile.v2.
2015-12-20 21:05:28 +03:00

752 lines
23 KiB
C++

/*
* Copyright Andrey Semashev 2007 - 2015.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*/
/*!
* \file named_scope_format_parser.cpp
* \author Andrey Semashev
* \date 14.11.2012
*
* \brief This header is the Boost.Log library implementation, see the library documentation
* at http://www.boost.org/doc/libs/release/libs/log/doc/html/index.html.
*/
#include <boost/log/detail/config.hpp>
#include <cstddef>
#include <cstring>
#include <string>
#include <vector>
#include <limits>
#include <algorithm>
#include <boost/cstdint.hpp>
#include <boost/move/core.hpp>
#include <boost/move/utility.hpp>
#include <boost/spirit/include/karma_uint.hpp>
#include <boost/spirit/include/karma_generate.hpp>
#include <boost/log/attributes/named_scope.hpp>
#include <boost/log/expressions/formatters/named_scope.hpp>
#include <boost/log/utility/formatting_ostream.hpp>
#include <boost/log/detail/header.hpp>
namespace karma = boost::spirit::karma;
namespace boost {
BOOST_LOG_OPEN_NAMESPACE
namespace expressions {
namespace aux {
BOOST_LOG_ANONYMOUS_NAMESPACE {
//! The function skips any spaces from the current position
BOOST_FORCEINLINE const char* skip_spaces(const char* p, const char* end)
{
while (p < end && *p == ' ')
++p;
return p;
}
//! The function checks if the given character can be part of a function/type/namespace name
BOOST_FORCEINLINE bool is_name_character(char c)
{
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= 'a' && c <= 'z');
}
//! The function checks if there is 'operator' keyword at the specified position
BOOST_FORCEINLINE bool is_operator_keyword(const char* p)
{
#if defined(__i386__) || defined(__x86_64__) || defined(_M_AMD64) || defined(_M_IX86)
// Intel architecture allows unaligned accesses, so just compare with the whole keyword at once
return *reinterpret_cast< const uint64_t* >(p) == UINT64_C(0x726f74617265706f);
#else
return std::memcmp(p, "operator", 8) == 0;
#endif
}
//! The function tries to parse operator signature
bool detect_operator(const char* begin, const char* end, const char* operator_keyword, const char*& operator_end)
{
if (end - operator_keyword < 9 || !is_operator_keyword(operator_keyword))
return false;
// Check that it's not a function name ending with 'operator', like detect_operator
if (operator_keyword > begin && is_name_character(*(operator_keyword - 1)))
return false;
const char* p = skip_spaces(operator_keyword + 8, end);
if (p == end)
return false;
// Check to see where the operator token ends
switch (*p)
{
case '(':
// Handle operator()
p = skip_spaces(++p, end);
if (p < end && *p == ')')
{
operator_end = p + 1;
return true;
}
return false;
case '[':
// Handle operator[]
p = skip_spaces(++p, end);
if (p < end && *p == ']')
{
operator_end = p + 1;
return true;
}
return false;
case '>':
case '<':
// Handle operator<=, operator>=, operator<<, operator>>, operator<<=, operator>>=
if (end - p >= 3 && (p[0] == p[1] && p[2] == '='))
operator_end = p + 3;
else if (end - p >= 2 && (p[0] == p[1] || p[1] == '='))
operator_end = p + 2;
else
operator_end = p + 1;
return true;
case '-':
// Handle operator->, operator->*
if (end - p >= 2 && p[1] == '>')
{
if (end - p >= 3 && p[2] == '*')
operator_end = p + 3;
else
operator_end = p + 2;
return true;
}
// Fall through to other cases involving '-'
case '=':
case '|':
case '&':
case '+':
// Handle operator=, operator==, operator+=, operator++, operator||, opeartor&&, etc.
if (end - p >= 2 && (p[0] == p[1] || p[1] == '='))
operator_end = p + 2;
else
operator_end = p + 1;
return true;
case '*':
case '/':
case '%':
case '^':
// Handle operator*, operator*=, etc.
if (end - p >= 2 && p[1] == '=')
operator_end = p + 2;
else
operator_end = p + 1;
return true;
case ',':
case '~':
case '!':
// Handle operator,, operator~, etc.
operator_end = p + 1;
return true;
case '"':
// Handle operator""
if (end - p >= 2 && p[0] == p[1])
{
p = skip_spaces(p + 2, end);
// Skip through the literal suffix
while (p < end && is_name_character(*p))
++p;
operator_end = p;
return true;
}
return false;
default:
// Handle type conversion operators. We can't find the end of the type reliably here.
operator_end = p;
return true;
}
}
//! The function skips all template parameters
inline const char* skip_template_parameters(const char* begin, const char* end)
{
unsigned int depth = 1;
const char* p = begin;
while (depth > 0 && p != end)
{
switch (*p)
{
case '>':
--depth;
break;
case '<':
++depth;
break;
case 'o':
{
// Skip operators (e.g. when an operator is a non-type template parameter)
const char* operator_end;
if (detect_operator(begin, end, p, operator_end))
{
p = operator_end;
continue;
}
}
break;
default:
break;
}
++p;
}
return p;
}
//! The function seeks for the opening parenthesis and also tries to find the function name beginning
inline const char* find_opening_parenthesis(const char* begin, const char* end, const char*& first_name_begin, const char*& last_name_begin)
{
enum sequence_state
{
not_started, // no significant (non-space) characters have been encountered so far
started, // some name has started; the name is a contiguous sequence of characters that may constitute a function or scope name
continued, // the previous characters were the scope operator ("::"), so the name is not finished yet
ended, // the name has ended; in particular, this means that there were significant characters previously in the string
operator_detected // operator has been found in the string, don't parse for scopes anymore; this is needed for conversion operators
};
sequence_state state = not_started;
const char* p = begin;
while (p != end)
{
char c = *p;
switch (c)
{
case '(':
if (state == not_started)
{
// If the opening brace is the first meaningful character in the string then this can't be a function signature.
// Pretend we didn't find the paranthesis to fail the parsing process.
return end;
}
return p;
case '<':
if (state == not_started)
{
// Template parameters cannot start as the first meaningful character in the signature.
// Pretend we didn't find the paranthesis to fail the parsing process.
return end;
}
p = skip_template_parameters(p + 1, end);
if (state != operator_detected)
state = ended;
continue;
case ' ':
if (state == started)
state = ended;
break;
case ':':
++p;
if (p != end && *p == ':')
{
if (state == not_started)
{
// Include the starting "::" in the full name
first_name_begin = p - 1;
}
if (state != operator_detected)
state = continued;
++p;
}
else if (state != operator_detected)
{
// Weird case, a single colon. Maybe, some compilers would put things like "public:" in front of the signature.
state = ended;
}
continue;
case 'o':
{
const char* operator_end;
if (detect_operator(begin, end, p, operator_end))
{
if (state == not_started || state == ended)
first_name_begin = p;
last_name_begin = p;
p = operator_end;
state = operator_detected;
continue;
}
}
// Fall through to process this character as other characters
default:
if (state != operator_detected)
{
if (is_name_character(c) || c == '~') // check for '~' in case of a destructor
{
if (state != started)
{
if (state == not_started || state == ended)
first_name_begin = p;
last_name_begin = p;
state = started;
}
}
else
{
state = ended;
}
}
break;
}
++p;
}
return p;
}
//! The function seeks for the closing parenthesis
inline const char* find_closing_parenthesis(const char* begin, const char* end, char& first_char)
{
bool found_first_meaningful_char = false;
unsigned int depth = 1;
const char* p = begin;
while (p != end)
{
char c = *p;
switch (c)
{
case ')':
--depth;
if (depth == 0)
return p;
break;
case '(':
++depth;
break;
case '<':
p = skip_template_parameters(p + 1, end);
continue;
case 'o':
{
const char* operator_end;
if (detect_operator(begin, end, p, operator_end))
{
p = operator_end;
continue;
}
}
// Fall through to process this character as other characters
default:
if (!found_first_meaningful_char && c != ' ')
{
found_first_meaningful_char = true;
first_char = c;
}
break;
}
++p;
}
return p;
}
bool parse_function_name(const char*& begin, const char*& end, bool include_scope)
{
// The algorithm tries to match several patterns to recognize function signatures. The most obvious is:
//
// A B(C)
//
// or just:
//
// B(C)
//
// in case of constructors, destructors and type conversion operators. The algorithm looks for the opening parenthesis and while doing that
// it detects the beginning of B. As a result B is the function name.
//
// The first significant complication is function and array return types, in which case the syntax becomes nested:
//
// A (*B(C))(D)
// A (&B(C))[D]
//
// In addition to that MSVC adds calling convention, such as __cdecl, to function types. In order to detect these cases the algorithm
// seeks for the closing parenthesis after the opening one. If there is an opening parenthesis or square bracket after the closing parenthesis
// then this is a function or array return type. The case of arrays is additionally complicated by GCC output:
//
// A B(C) [D]
//
// where D is template parameters description and is not part of the signature. To discern this special case from the array return type, the algorithm
// checks for the first significant character within the parenthesis. This character is '&' in case of arrays and something else otherwise.
//
// Speaking of template parameters, the parsing algorithm ignores them completely, assuming they are part of the name being parsed. This includes
// any possible parenthesis, nested template parameters and even operators, which may be present there as non-type template parameters.
//
// Operators pose another problem. This is especially the case for type conversion operators, and even more so for conversion operators to
// function types. In this latter case at least MSVC is known to produce incomprehensible strings which we cannot parse. In other cases it is
// too difficult to parse the type correctly. So we cheat a little. Whenever we find "operator", we know that we've found the function name
// already, and the name ends at the opening parenthesis. For other operators we are able to parse them correctly but that doesn't really matter.
//
// Note that the algorithm should be tolerant to different flavors of the input strings from different compilers, so we can't rely on spaces
// delimiting function names and other elements. Also, the algorithm should behave well in case of the fallback string generated by
// BOOST_CURRENT_FUNCTION (which is "(unknown)" currently). In case of any parsing failure the algorithm should return false, in which case the
// full original string will be used as the output.
const char* b = begin;
const char* e = end;
while (b != e)
{
// Find the opening parenthesis. While looking for it, also find the function name.
// first_name_begin is the beginning of the function scope, last_name_begin is the actual function name.
const char* first_name_begin = NULL, *last_name_begin = NULL;
const char* paren_open = find_opening_parenthesis(b, e, first_name_begin, last_name_begin);
if (paren_open == e)
return false;
// Find the closing parenthesis. Also peek at the first character in the parenthesis, which we'll use to detect array return types.
char first_char_in_parenthesis = 0;
const char* paren_close = find_closing_parenthesis(paren_open + 1, e, first_char_in_parenthesis);
if (paren_close == e)
return false;
const char* p = skip_spaces(paren_close + 1, e);
// Detect function and array return types
if (p < e && (*p == '(' || (*p == '[' && first_char_in_parenthesis == '&')))
{
// This is a function or array return type, the actual function name is within the parenthesis.
// Re-parse the string within the parenthesis as a function signature.
b = paren_open + 1;
e = paren_close;
continue;
}
// We found something that looks like a function signature
if (include_scope)
{
if (!first_name_begin)
return false;
begin = first_name_begin;
}
else
{
if (!last_name_begin)
return false;
begin = last_name_begin;
}
end = paren_open;
return true;
}
return false;
}
template< typename CharT >
class named_scope_formatter
{
BOOST_COPYABLE_AND_MOVABLE_ALT(named_scope_formatter)
public:
typedef void result_type;
typedef CharT char_type;
typedef std::basic_string< char_type > string_type;
typedef basic_formatting_ostream< char_type > stream_type;
typedef attributes::named_scope::value_type::value_type value_type;
struct literal
{
typedef void result_type;
explicit literal(string_type& lit) { m_literal.swap(lit); }
result_type operator() (stream_type& strm, value_type const&) const
{
strm << m_literal;
}
private:
string_type m_literal;
};
struct scope_name
{
typedef void result_type;
result_type operator() (stream_type& strm, value_type const& value) const
{
strm << value.scope_name;
}
};
struct function_name
{
typedef void result_type;
explicit function_name(bool include_scope) : m_include_scope(include_scope)
{
}
result_type operator() (stream_type& strm, value_type const& value) const
{
if (value.type == attributes::named_scope_entry::function)
{
const char* begin = value.scope_name.c_str();
const char* end = begin + value.scope_name.size();
if (parse_function_name(begin, end, m_include_scope))
{
strm.write(begin, end - begin);
return;
}
}
strm << value.scope_name;
}
private:
const bool m_include_scope;
};
struct full_file_name
{
typedef void result_type;
result_type operator() (stream_type& strm, value_type const& value) const
{
strm << value.file_name;
}
};
struct file_name
{
typedef void result_type;
result_type operator() (stream_type& strm, value_type const& value) const
{
std::size_t n = value.file_name.size(), i = n;
for (; i > 0; --i)
{
const char c = value.file_name[i - 1];
#if defined(BOOST_WINDOWS)
if (c == '\\')
break;
#endif
if (c == '/')
break;
}
strm.write(value.file_name.c_str() + i, n - i);
}
};
struct line_number
{
typedef void result_type;
result_type operator() (stream_type& strm, value_type const& value) const
{
strm.flush();
typedef typename stream_type::streambuf_type streambuf_type;
string_type& str = *static_cast< streambuf_type* >(strm.rdbuf())->storage();
char_type buf[std::numeric_limits< unsigned int >::digits10 + 2];
char_type* p = buf;
typedef karma::uint_generator< unsigned int, 10 > uint_gen;
karma::generate(p, uint_gen(), value.line);
str.append(buf, p);
}
};
private:
typedef boost::log::aux::light_function< void (stream_type&, value_type const&) > formatter_type;
typedef std::vector< formatter_type > formatters;
private:
formatters m_formatters;
public:
BOOST_DEFAULTED_FUNCTION(named_scope_formatter(), {})
named_scope_formatter(named_scope_formatter const& that) : m_formatters(that.m_formatters) {}
named_scope_formatter(BOOST_RV_REF(named_scope_formatter) that) { m_formatters.swap(that.m_formatters); }
named_scope_formatter& operator= (named_scope_formatter that)
{
this->swap(that);
return *this;
}
result_type operator() (stream_type& strm, value_type const& value) const
{
for (typename formatters::const_iterator it = m_formatters.begin(), end = m_formatters.end(); strm.good() && it != end; ++it)
{
(*it)(strm, value);
}
}
#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
template< typename FunT >
void add_formatter(FunT&& fun)
{
m_formatters.emplace_back(boost::forward< FunT >(fun));
}
#else
template< typename FunT >
void add_formatter(FunT const& fun)
{
m_formatters.push_back(formatter_type(fun));
}
#endif
void swap(named_scope_formatter& that)
{
m_formatters.swap(that.m_formatters);
}
};
//! Parses the named scope format string and constructs the formatter function
template< typename CharT >
BOOST_FORCEINLINE boost::log::aux::light_function< void (basic_formatting_ostream< CharT >&, attributes::named_scope::value_type::value_type const&) >
do_parse_named_scope_format(const CharT* begin, const CharT* end)
{
typedef CharT char_type;
typedef boost::log::aux::light_function< void (basic_formatting_ostream< char_type >&, attributes::named_scope::value_type::value_type const&) > result_type;
typedef named_scope_formatter< char_type > formatter_type;
formatter_type fmt;
std::basic_string< char_type > literal;
while (begin != end)
{
const char_type* p = std::find(begin, end, static_cast< char_type >('%'));
literal.append(begin, p);
if ((end - p) >= 2)
{
switch (p[1])
{
case '%':
literal.push_back(static_cast< char_type >('%'));
break;
case 'n':
if (!literal.empty())
fmt.add_formatter(typename formatter_type::literal(literal));
fmt.add_formatter(typename formatter_type::scope_name());
break;
case 'c':
if (!literal.empty())
fmt.add_formatter(typename formatter_type::literal(literal));
fmt.add_formatter(typename formatter_type::function_name(true));
break;
case 'C':
if (!literal.empty())
fmt.add_formatter(typename formatter_type::literal(literal));
fmt.add_formatter(typename formatter_type::function_name(false));
break;
case 'f':
if (!literal.empty())
fmt.add_formatter(typename formatter_type::literal(literal));
fmt.add_formatter(typename formatter_type::full_file_name());
break;
case 'F':
if (!literal.empty())
fmt.add_formatter(typename formatter_type::literal(literal));
fmt.add_formatter(typename formatter_type::file_name());
break;
case 'l':
if (!literal.empty())
fmt.add_formatter(typename formatter_type::literal(literal));
fmt.add_formatter(typename formatter_type::line_number());
break;
default:
literal.append(p, p + 2);
break;
}
begin = p + 2;
}
else
{
if (p != end)
literal.push_back(static_cast< char_type >('%')); // a single '%' character at the end of the string
begin = end;
}
}
if (!literal.empty())
fmt.add_formatter(typename formatter_type::literal(literal));
return result_type(boost::move(fmt));
}
} // namespace
#ifdef BOOST_LOG_USE_CHAR
//! Parses the named scope format string and constructs the formatter function
BOOST_LOG_API boost::log::aux::light_function< void (basic_formatting_ostream< char >&, attributes::named_scope::value_type::value_type const&) >
parse_named_scope_format(const char* begin, const char* end)
{
return do_parse_named_scope_format(begin, end);
}
#endif // BOOST_LOG_USE_CHAR
#ifdef BOOST_LOG_USE_WCHAR_T
//! Parses the named scope format string and constructs the formatter function
BOOST_LOG_API boost::log::aux::light_function< void (basic_formatting_ostream< wchar_t >&, attributes::named_scope::value_type::value_type const&) >
parse_named_scope_format(const wchar_t* begin, const wchar_t* end)
{
return do_parse_named_scope_format(begin, end);
}
#endif // BOOST_LOG_USE_WCHAR_T
} // namespace aux
} // namespace expressions
BOOST_LOG_CLOSE_NAMESPACE // namespace log
} // namespace boost
#include <boost/log/detail/footer.hpp>