| /* |
| * 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 filter_parser.cpp |
| * \author Andrey Semashev |
| * \date 31.03.2008 |
| * |
| * \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. |
| */ |
| |
| #ifndef BOOST_LOG_WITHOUT_SETTINGS_PARSERS |
| |
| #include <cstddef> |
| #include <map> |
| #include <stack> |
| #include <string> |
| #include <sstream> |
| #include <stdexcept> |
| #include <utility> |
| #include <boost/assert.hpp> |
| #include <boost/none.hpp> |
| #include <boost/move/core.hpp> |
| #include <boost/move/utility.hpp> |
| #include <boost/optional/optional.hpp> |
| #include <boost/utility/in_place_factory.hpp> |
| #include <boost/phoenix/core.hpp> |
| #include <boost/phoenix/bind/bind_function_object.hpp> |
| #include <boost/phoenix/operator/logical.hpp> |
| #include <boost/log/detail/singleton.hpp> |
| #include <boost/log/exceptions.hpp> |
| #include <boost/log/utility/setup/filter_parser.hpp> |
| #if !defined(BOOST_LOG_NO_THREADS) |
| #include <boost/log/detail/locks.hpp> |
| #include <boost/log/detail/light_rw_mutex.hpp> |
| #endif // !defined(BOOST_LOG_NO_THREADS) |
| #include "parser_utils.hpp" |
| #if !defined(BOOST_LOG_WITHOUT_DEFAULT_FACTORIES) |
| #include "default_filter_factory.hpp" |
| #endif |
| #include "spirit_encoding.hpp" |
| #include <boost/log/detail/header.hpp> |
| |
| namespace boost { |
| |
| BOOST_LOG_OPEN_NAMESPACE |
| |
| BOOST_LOG_ANONYMOUS_NAMESPACE { |
| |
| //! Filter factories repository |
| template< typename CharT > |
| struct filters_repository : |
| public log::aux::lazy_singleton< filters_repository< CharT > > |
| { |
| typedef CharT char_type; |
| typedef log::aux::lazy_singleton< filters_repository< char_type > > base_type; |
| typedef std::basic_string< char_type > string_type; |
| typedef filter_factory< char_type > filter_factory_type; |
| |
| //! Attribute name ordering predicate |
| struct attribute_name_order |
| { |
| typedef bool result_type; |
| result_type operator() (attribute_name const& left, attribute_name const& right) const |
| { |
| return left.id() < right.id(); |
| } |
| }; |
| |
| typedef std::map< attribute_name, shared_ptr< filter_factory_type >, attribute_name_order > factories_map; |
| |
| #if !defined(BOOST_LOG_BROKEN_FRIEND_TEMPLATE_SPECIALIZATIONS) |
| friend class log::aux::lazy_singleton< filters_repository< char_type > >; |
| #else |
| friend class base_type; |
| #endif |
| |
| #if !defined(BOOST_LOG_NO_THREADS) |
| //! Synchronization mutex |
| mutable log::aux::light_rw_mutex m_Mutex; |
| #endif |
| //! The map of filter factories |
| factories_map m_Map; |
| #if !defined(BOOST_LOG_WITHOUT_DEFAULT_FACTORIES) |
| //! Default factory |
| mutable aux::default_filter_factory< char_type > m_DefaultFactory; |
| #endif |
| |
| //! The method returns the filter factory for the specified attribute name |
| filter_factory_type& get_factory(attribute_name const& name) const |
| { |
| typename factories_map::const_iterator it = m_Map.find(name); |
| if (it != m_Map.end()) |
| { |
| return *it->second; |
| } |
| else |
| { |
| #if !defined(BOOST_LOG_WITHOUT_DEFAULT_FACTORIES) |
| return m_DefaultFactory; |
| #else |
| BOOST_LOG_THROW_DESCR(setup_error, "No filter factory registered for attribute " + name.string()); |
| #endif |
| } |
| } |
| |
| private: |
| filters_repository() |
| { |
| } |
| }; |
| |
| //! Filter parser |
| template< typename CharT > |
| class filter_parser |
| { |
| private: |
| typedef CharT char_type; |
| typedef const char_type* iterator_type; |
| typedef typename log::aux::encoding< char_type >::type encoding; |
| typedef log::aux::encoding_specific< encoding > encoding_specific; |
| typedef std::basic_string< char_type > string_type; |
| typedef log::aux::char_constants< char_type > constants; |
| typedef filter_factory< char_type > filter_factory_type; |
| |
| typedef filter (filter_factory_type::*comparison_relation_handler_t)(attribute_name const&, string_type const&); |
| |
| private: |
| //! Parsed attribute name |
| mutable attribute_name m_AttributeName; |
| //! The second operand of a relation |
| mutable optional< string_type > m_Operand; |
| //! Comparison relation handler |
| comparison_relation_handler_t m_ComparisonRelation; |
| //! The custom relation string |
| mutable string_type m_CustomRelation; |
| |
| //! Filter subexpressions as they are parsed |
| mutable std::stack< filter > m_Subexpressions; |
| |
| public: |
| //! Constructor |
| filter_parser() : |
| m_ComparisonRelation(NULL) |
| { |
| } |
| |
| //! The method returns the constructed filter |
| filter get_filter() |
| { |
| if (m_Subexpressions.empty()) |
| return filter(); |
| return boost::move(m_Subexpressions.top()); |
| } |
| |
| //! The pethod parses filter from the string |
| void parse(iterator_type& begin, iterator_type end, unsigned int depth = 0) |
| { |
| typedef void (filter_parser::*logical_op_t)(); |
| logical_op_t logical_op = NULL; |
| iterator_type p = constants::trim_spaces_left(begin, end); |
| while (p != end) |
| { |
| // Parse subexpression |
| parse_subexpression(p, end, depth); |
| if (logical_op) |
| { |
| // This was the right-hand subexpression. Compose the two top subexpressions into a single filter. |
| (this->*logical_op)(); |
| logical_op = NULL; |
| } |
| |
| p = constants::trim_spaces_left(p, end); |
| if (p != end) |
| { |
| char_type c = *p; |
| iterator_type next = p + 1; |
| if (c == constants::char_paren_bracket_right) |
| { |
| // The subexpression has ended |
| if (depth == 0) |
| BOOST_LOG_THROW_DESCR(parse_error, "Invalid filter definition: unmatched closing parenthesis"); |
| |
| p = next; |
| --depth; |
| break; |
| } |
| else if (c == constants::char_and || scan_keyword(p, end, next, constants::and_keyword())) |
| { |
| logical_op = &filter_parser::on_and; |
| } |
| else if (c == constants::char_or || scan_keyword(p, end, next, constants::or_keyword())) |
| { |
| logical_op = &filter_parser::on_or; |
| } |
| else |
| { |
| BOOST_LOG_THROW_DESCR(parse_error, "Invalid filter definition: unexpected character encountered"); |
| } |
| |
| p = constants::trim_spaces_left(next, end); |
| } |
| else |
| break; |
| } |
| |
| if (logical_op) |
| { |
| BOOST_LOG_THROW_DESCR(parse_error, "Invalid filter definition: logical operation without the right-hand subexpression"); |
| } |
| |
| if (p == end && depth > 0) |
| { |
| BOOST_LOG_THROW_DESCR(parse_error, "Invalid filter definition: unterminated parenthesis"); |
| } |
| |
| begin = p; |
| } |
| |
| private: |
| //! The method parses a single subexpression |
| void parse_subexpression(iterator_type& begin, iterator_type end, unsigned int depth) |
| { |
| bool negated = false, negation_present = false, done = false; |
| iterator_type p = begin; |
| |
| while (p != end) |
| { |
| char_type c = *p; |
| iterator_type next = p + 1; |
| if (c == constants::char_percent) |
| { |
| // We found an attribute placeholder |
| iterator_type start = constants::trim_spaces_left(next, end); |
| p = constants::scan_attr_placeholder(start, end); |
| if (p == end) |
| BOOST_LOG_THROW_DESCR(parse_error, "Invalid attribute placeholder in the filter string"); |
| |
| on_attribute_name(start, p); |
| |
| p = constants::trim_spaces_left(p, end); |
| if (p == end || *p != constants::char_percent) |
| BOOST_LOG_THROW_DESCR(parse_error, "Invalid attribute placeholder in the filter string"); |
| |
| // Skip the closing char_percent |
| p = constants::trim_spaces_left(++p, end); |
| |
| // If the filter has negation operator, do not expect a relation (i.e. "!%attr% > 1" is not valid because "!%attr%" is interpreted as an attribute presence test) |
| if (!negation_present) |
| p = parse_relation(p, end); |
| else |
| on_relation_complete(); |
| } |
| else if (c == constants::char_exclamation || scan_keyword(p, end, next, constants::not_keyword())) |
| { |
| // We found negation operation. Parse the subexpression to be negated. |
| negated ^= true; |
| negation_present = true; |
| p = constants::trim_spaces_left(next, end); |
| continue; |
| } |
| else if (c == constants::char_paren_bracket_left) |
| { |
| // We found a nested subexpression |
| parse(next, end, depth + 1); |
| p = next; |
| } |
| else |
| { |
| BOOST_LOG_THROW_DESCR(parse_error, "Invalid filter definition: unexpected character"); |
| } |
| |
| if (negated) |
| on_negation(); |
| |
| done = true; |
| |
| break; |
| } |
| |
| if (negation_present && !done) |
| { |
| // This would happen if a filter consists of a single '!' |
| BOOST_LOG_THROW_DESCR(parse_error, "Filter parsing error: negation operator applied to nothingness"); |
| } |
| |
| begin = p; |
| } |
| |
| //! Parses filtering relation |
| iterator_type parse_relation(iterator_type begin, iterator_type end) |
| { |
| iterator_type p = begin; |
| if (p != end) |
| { |
| iterator_type next = p; |
| if (scan_keyword(p, end, next, constants::equal_keyword())) |
| m_ComparisonRelation = &filter_factory_type::on_equality_relation; |
| else if (scan_keyword(p, end, next, constants::not_equal_keyword())) |
| m_ComparisonRelation = &filter_factory_type::on_inequality_relation; |
| else if (scan_keyword(p, end, next, constants::greater_keyword())) |
| m_ComparisonRelation = &filter_factory_type::on_greater_relation; |
| else if (scan_keyword(p, end, next, constants::less_keyword())) |
| m_ComparisonRelation = &filter_factory_type::on_less_relation; |
| else if (scan_keyword(p, end, next, constants::greater_or_equal_keyword())) |
| m_ComparisonRelation = &filter_factory_type::on_greater_or_equal_relation; |
| else if (scan_keyword(p, end, next, constants::less_or_equal_keyword())) |
| m_ComparisonRelation = &filter_factory_type::on_less_or_equal_relation; |
| else |
| { |
| // Check for custom relation |
| while (next != end && (encoding::isalnum(*next) || *next == constants::char_underline)) |
| ++next; |
| if (p == next) |
| goto DoneL; |
| m_CustomRelation.assign(p, next); |
| } |
| |
| // We have parsed a relation operator, there must be an operand |
| next = constants::trim_spaces_left(next, end); |
| string_type operand; |
| p = constants::parse_operand(next, end, operand); |
| if (next == p) |
| BOOST_LOG_THROW_DESCR(parse_error, "Missing operand for a relation in the filter string"); |
| |
| m_Operand = boost::in_place(operand); |
| } |
| |
| DoneL: |
| // The relation can be as simple as a sole attribute placeholder (which means that only attribute presence has to be checked). |
| // So regardless how we get here, the relation is parsed completely. |
| on_relation_complete(); |
| |
| return p; |
| } |
| |
| //! Checks if the string contains a keyword |
| static bool scan_keyword(iterator_type begin, iterator_type end, iterator_type& next, iterator_type keyword) |
| { |
| for (iterator_type p = begin; p != end; ++p, ++keyword) |
| { |
| char_type c1 = *p, c2 = *keyword; |
| if (c2 == 0) |
| { |
| if (encoding::isspace(c1)) |
| { |
| next = p; |
| return true; |
| } |
| break; |
| } |
| if (c1 != c2) |
| break; |
| } |
| |
| return false; |
| } |
| |
| //! The attribute name handler |
| void on_attribute_name(iterator_type begin, iterator_type end) |
| { |
| if (begin == end) |
| BOOST_LOG_THROW_DESCR(parse_error, "Empty attribute name encountered"); |
| m_AttributeName = attribute_name(log::aux::to_narrow(string_type(begin, end))); |
| } |
| |
| //! The comparison relation handler |
| void on_relation_complete() |
| { |
| if (!!m_AttributeName) |
| { |
| filters_repository< char_type > const& repo = filters_repository< char_type >::get(); |
| filter_factory_type& factory = repo.get_factory(m_AttributeName); |
| |
| if (!!m_Operand) |
| { |
| if (!!m_ComparisonRelation) |
| { |
| m_Subexpressions.push((factory.*m_ComparisonRelation)(m_AttributeName, m_Operand.get())); |
| m_ComparisonRelation = NULL; |
| } |
| else if (!m_CustomRelation.empty()) |
| { |
| m_Subexpressions.push(factory.on_custom_relation(m_AttributeName, m_CustomRelation, m_Operand.get())); |
| m_CustomRelation.clear(); |
| } |
| else |
| { |
| // This should never happen |
| BOOST_ASSERT_MSG(false, "Filter parser internal error: the attribute name or subexpression operation is not set while trying to construct a relation"); |
| BOOST_LOG_THROW_DESCR(parse_error, "Filter parser internal error: the attribute name or subexpression operation is not set while trying to construct a subexpression"); |
| } |
| |
| m_Operand = none; |
| } |
| else |
| { |
| // This branch is taken if the relation is a single attribute name, which is recognized as the attribute presence check |
| BOOST_ASSERT_MSG(!m_ComparisonRelation && m_CustomRelation.empty(), "Filter parser internal error: the relation operation is set while operand is not"); |
| m_Subexpressions.push(factory.on_exists_test(m_AttributeName)); |
| } |
| |
| m_AttributeName = attribute_name(); |
| } |
| else |
| { |
| // This should never happen |
| BOOST_ASSERT_MSG(false, "Filter parser internal error: the attribute name is not set while trying to construct a relation"); |
| BOOST_LOG_THROW_DESCR(parse_error, "Filter parser internal error: the attribute name is not set while trying to construct a relation"); |
| } |
| } |
| |
| //! The negation operation handler |
| void on_negation() |
| { |
| if (!m_Subexpressions.empty()) |
| { |
| m_Subexpressions.top() = !phoenix::bind(m_Subexpressions.top(), phoenix::placeholders::_1); |
| } |
| else |
| { |
| // This would happen if a filter consists of "!()" |
| BOOST_LOG_THROW_DESCR(parse_error, "Filter parsing error: negation operator applied to an empty subexpression"); |
| } |
| } |
| |
| //! The logical AND operation handler |
| void on_and() |
| { |
| if (!m_Subexpressions.empty()) |
| { |
| filter right = boost::move(m_Subexpressions.top()); |
| m_Subexpressions.pop(); |
| if (!m_Subexpressions.empty()) |
| { |
| filter const& left = m_Subexpressions.top(); |
| m_Subexpressions.top() = phoenix::bind(left, phoenix::placeholders::_1) && phoenix::bind(right, phoenix::placeholders::_1); |
| return; |
| } |
| } |
| |
| // This should never happen |
| BOOST_LOG_THROW_DESCR(parse_error, "Filter parser internal error: the subexpression is not set while trying to construct a filter"); |
| } |
| |
| //! The logical OR operation handler |
| void on_or() |
| { |
| if (!m_Subexpressions.empty()) |
| { |
| filter right = boost::move(m_Subexpressions.top()); |
| m_Subexpressions.pop(); |
| if (!m_Subexpressions.empty()) |
| { |
| filter const& left = m_Subexpressions.top(); |
| m_Subexpressions.top() = phoenix::bind(left, phoenix::placeholders::_1) || phoenix::bind(right, phoenix::placeholders::_1); |
| return; |
| } |
| } |
| |
| // This should never happen |
| BOOST_LOG_THROW_DESCR(parse_error, "Filter parser internal error: the subexpression is not set while trying to construct a filter"); |
| } |
| |
| // Assignment and copying are prohibited |
| BOOST_DELETED_FUNCTION(filter_parser(filter_parser const&)) |
| BOOST_DELETED_FUNCTION(filter_parser& operator= (filter_parser const&)) |
| }; |
| |
| } // namespace |
| |
| //! The function registers a filter factory object for the specified attribute name |
| template< typename CharT > |
| void register_filter_factory(attribute_name const& name, shared_ptr< filter_factory< CharT > > const& factory) |
| { |
| BOOST_ASSERT(!!name); |
| BOOST_ASSERT(!!factory); |
| |
| filters_repository< CharT >& repo = filters_repository< CharT >::get(); |
| |
| BOOST_LOG_EXPR_IF_MT(log::aux::exclusive_lock_guard< log::aux::light_rw_mutex > lock(repo.m_Mutex);) |
| repo.m_Map[name] = factory; |
| } |
| |
| //! The function parses a filter from the string |
| template< typename CharT > |
| filter parse_filter(const CharT* begin, const CharT* end) |
| { |
| typedef CharT char_type; |
| typedef log::aux::encoding_specific< typename log::aux::encoding< char_type >::type > encoding_specific; |
| |
| filter_parser< char_type > parser; |
| const char_type* p = begin; |
| |
| BOOST_LOG_EXPR_IF_MT(filters_repository< CharT >& repo = filters_repository< CharT >::get();) |
| BOOST_LOG_EXPR_IF_MT(log::aux::shared_lock_guard< log::aux::light_rw_mutex > lock(repo.m_Mutex);) |
| |
| parser.parse(p, end); |
| |
| return parser.get_filter(); |
| } |
| |
| #ifdef BOOST_LOG_USE_CHAR |
| |
| template BOOST_LOG_SETUP_API |
| void register_filter_factory< char >(attribute_name const& name, shared_ptr< filter_factory< char > > const& factory); |
| template BOOST_LOG_SETUP_API |
| filter parse_filter< char >(const char* begin, const char* end); |
| |
| #endif // BOOST_LOG_USE_CHAR |
| |
| #ifdef BOOST_LOG_USE_WCHAR_T |
| |
| template BOOST_LOG_SETUP_API |
| void register_filter_factory< wchar_t >(attribute_name const& name, shared_ptr< filter_factory< wchar_t > > const& factory); |
| template BOOST_LOG_SETUP_API |
| filter parse_filter< wchar_t >(const wchar_t* begin, const wchar_t* end); |
| |
| #endif // BOOST_LOG_USE_WCHAR_T |
| |
| BOOST_LOG_CLOSE_NAMESPACE // namespace log |
| |
| } // namespace boost |
| |
| #include <boost/log/detail/footer.hpp> |
| |
| #endif // BOOST_LOG_WITHOUT_SETTINGS_PARSERS |