1 /*
2  *  Created by Phil on 15/5/2013.
3  *  Copyright 2014 Two Blue Cubes Ltd. All rights reserved.
4  *
5  *  Distributed under the Boost Software License, Version 1.0. (See accompanying
6  *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  */
8 #ifndef TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED
9 #define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED
10 
11 #ifdef __clang__
12 #pragma clang diagnostic push
13 #pragma clang diagnostic ignored "-Wpadded"
14 #endif
15 
16 #include "catch_test_spec.hpp"
17 #include "catch_interfaces_tag_alias_registry.h"
18 
19 namespace Catch {
20 
21     class TestSpecParser {
22         enum Mode{ None, Name, QuotedName, Tag, EscapedName };
23         Mode m_mode;
24         bool m_exclusion;
25         std::size_t m_start, m_pos;
26         std::string m_arg;
27         std::vector<std::size_t> m_escapeChars;
28         TestSpec::Filter m_currentFilter;
29         TestSpec m_testSpec;
30         ITagAliasRegistry const* m_tagAliases;
31 
32     public:
TestSpecParser(ITagAliasRegistry const & tagAliases)33         TestSpecParser( ITagAliasRegistry const& tagAliases ) :m_mode(None), m_exclusion(false), m_start(0), m_pos(0), m_tagAliases( &tagAliases ) {}
34 
parse(std::string const & arg)35         TestSpecParser& parse( std::string const& arg ) {
36             m_mode = None;
37             m_exclusion = false;
38             m_start = std::string::npos;
39             m_arg = m_tagAliases->expandAliases( arg );
40             m_escapeChars.clear();
41             for( m_pos = 0; m_pos < m_arg.size(); ++m_pos )
42                 visitChar( m_arg[m_pos] );
43             if( m_mode == Name )
44                 addPattern<TestSpec::NamePattern>();
45             return *this;
46         }
testSpec()47         TestSpec testSpec() {
48             addFilter();
49             return m_testSpec;
50         }
51     private:
visitChar(char c)52         void visitChar( char c ) {
53             if( m_mode == None ) {
54                 switch( c ) {
55                 case ' ': return;
56                 case '~': m_exclusion = true; return;
57                 case '[': return startNewMode( Tag, ++m_pos );
58                 case '"': return startNewMode( QuotedName, ++m_pos );
59                 case '\\': return escape();
60                 default: startNewMode( Name, m_pos ); break;
61                 }
62             }
63             if( m_mode == Name ) {
64                 if( c == ',' ) {
65                     addPattern<TestSpec::NamePattern>();
66                     addFilter();
67                 }
68                 else if( c == '[' ) {
69                     if( subString() == "exclude:" )
70                         m_exclusion = true;
71                     else
72                         addPattern<TestSpec::NamePattern>();
73                     startNewMode( Tag, ++m_pos );
74                 }
75                 else if( c == '\\' )
76                     escape();
77             }
78             else if( m_mode == EscapedName )
79                 m_mode = Name;
80             else if( m_mode == QuotedName && c == '"' )
81                 addPattern<TestSpec::NamePattern>();
82             else if( m_mode == Tag && c == ']' )
83                 addPattern<TestSpec::TagPattern>();
84         }
startNewMode(Mode mode,std::size_t start)85         void startNewMode( Mode mode, std::size_t start ) {
86             m_mode = mode;
87             m_start = start;
88         }
escape()89         void escape() {
90             if( m_mode == None )
91                 m_start = m_pos;
92             m_mode = EscapedName;
93             m_escapeChars.push_back( m_pos );
94         }
subString() const95         std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); }
96         template<typename T>
addPattern()97         void addPattern() {
98             std::string token = subString();
99             for( size_t i = 0; i < m_escapeChars.size(); ++i )
100                 token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 );
101             m_escapeChars.clear();
102             if( startsWith( token, "exclude:" ) ) {
103                 m_exclusion = true;
104                 token = token.substr( 8 );
105             }
106             if( !token.empty() ) {
107                 Ptr<TestSpec::Pattern> pattern = new T( token );
108                 if( m_exclusion )
109                     pattern = new TestSpec::ExcludedPattern( pattern );
110                 m_currentFilter.m_patterns.push_back( pattern );
111             }
112             m_exclusion = false;
113             m_mode = None;
114         }
addFilter()115         void addFilter() {
116             if( !m_currentFilter.m_patterns.empty() ) {
117                 m_testSpec.m_filters.push_back( m_currentFilter );
118                 m_currentFilter = TestSpec::Filter();
119             }
120         }
121     };
parseTestSpec(std::string const & arg)122     inline TestSpec parseTestSpec( std::string const& arg ) {
123         return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec();
124     }
125 
126 } // namespace Catch
127 
128 #ifdef __clang__
129 #pragma clang diagnostic pop
130 #endif
131 
132 #endif // TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED
133