aql: prompt completion redesign

parent a1b3eed6
......@@ -37,12 +37,12 @@ std::istream& operator>> ( std::istream & in, std::pair < T, U > & value ) {
#include <global/GlobalData.h>
#include "prompt/Prompt.h"
#include "prompt/ReadlineLineInterface.h"
#include "prompt/readline/ReadlineLineInterface.h"
#include <readline/IstreamLineInterface.h>
#include <readline/StringLineInterface.h>
#include <prompt/ReadlinePromptHistory.h>
#include <prompt/readline/ReadlinePromptHistory.h>
#include <common/ResultInterpret.h>
......
/*
* ReadlinePromptCompletion.cpp
*
* Created on: 22. 1. 2019
* Author: Tomas Pecka
*/
#include <alib/registration>
#include "ReadlinePromptCompletion.h"
static ext::Register < void > instance ( [ ] ( ) {
// register readline completion function, pass environment
rl_attempted_completion_function = ReadlinePromptCompletion::readline_completion;
} );
/* ========================================================================= */
std::set < std::string > ReadlinePromptCompletion::addPrefix ( const std::set < std::string > & collection, const std::string & prefix ) {
std::set < std::string > res;
for ( const std::string & s : collection )
res.insert ( prefix + s );
return res;
}
std::set < std::string > ReadlinePromptCompletion::getGroups ( const std::string & qualified_name ) {
std::set < std::string > res;
unsigned template_level = 0;
for ( size_t i = 0; i < qualified_name.size ( ); i++ ) {
if ( qualified_name [ i ] == '<' )
template_level ++;
else if ( qualified_name [ i ] == '>' )
template_level --;
else if ( template_level == 0 && i > 0 && qualified_name [ i - 1 ] == ':' && qualified_name [ i ] == ':' )
res.insert ( qualified_name.substr ( 0, i + 1 ) );
}
return res;
}
std::set < std::string > ReadlinePromptCompletion::filter_completions ( const std::set < std::string > & choices, const char *text ) {
std::pair < std::set < std::string > :: const_iterator, std::set < std::string > :: const_iterator > range;
const std::string prefix = text;
range = std::equal_range ( choices.begin ( ), choices.end ( ), prefix,
[ &prefix ] ( const std::string & a, const std::string & b ) { return strncmp ( a.c_str ( ), b.c_str ( ), prefix.size ( )) < 0; } );
return std::set < std::string > ( range.first, range.second );
}
/* ========================================================================= */
bool ReadlinePromptCompletion::masterCommandCompletionTest ( const ext::string & line, const int start, const std::string & expectation ) {
return line.starts_with ( expectation ) && ( unsigned ) start == expectation.length ( ) + 1;
}
ReadlinePromptCompletion::CompletionContext ReadlinePromptCompletion::context ( const char *text, const int start, const int end ) {
ext::string line ( rl_line_buffer );
if ( start == 0 )
return CompletionContext::COMMAND;
if ( masterCommandCompletionTest ( line, start, "introspect" ) )
return CompletionContext::COMMAND_INTROSPECT;
else if ( masterCommandCompletionTest ( line, start, "set" ) )
return CompletionContext::SET;
else if ( masterCommandCompletionTest ( line, start, "introspect overloads" ) )
return CompletionContext::ALGORITHM;
else if ( masterCommandCompletionTest ( line, start, "introspect variables" ) )
return CompletionContext::VARIABLE;
else if ( masterCommandCompletionTest ( line, start, "introspect bindings" ) )
return CompletionContext::BINDING;
else if ( masterCommandCompletionTest ( line, start, "introspect algorithms" ) )
return CompletionContext::ALGORITHM_GROUP;
else if ( masterCommandCompletionTest ( line, start, "introspect datatypes" ) )
return CompletionContext::DATATYPE_GROUP;
/* TODO
else if ( masterCommandCompletionTest ( line, start, "introspect casts" ) )
*/
if ( end - start > 0 && text [ 0 ] == '$' )
return CompletionContext::VARIABLE;
else if ( end - start > 0 && text [ 0 ] == '#' )
return CompletionContext::BINDING;
/* scan the context backwards. If first non-whitespace character is < or >, then complete a filename */
char *p = rl_line_buffer + start - 1;
while ( p >= rl_line_buffer && ext::isspace ( *p ) ) p --;
if ( p >= rl_line_buffer && ( *p == '<' || *p == '>' ) )
return CompletionContext::FILEPATH_OR_VARIABLE;
if ( p >= rl_line_buffer && ( *p == '|' || *p == '(' ) )
return CompletionContext::ALGORITHM;
if ( masterCommandCompletionTest ( line, start, "execute" ) )
return CompletionContext::ALGORITHM;
/* undecided, fallback to filepath */
return CompletionContext::FILEPATH;
}
/* ========================================================================= */
char ** ReadlinePromptCompletion::readline_completion ( const char *text, int start, int end ) {
// for variables and bindings
rl_special_prefixes = "$#@";
rl_attempted_completion_over = 1;
// std::cerr << ">" << text << "< " << start << " " << end << std::endl;
switch ( context ( text, start, end ) ) {
case CompletionContext::ALGORITHM:
return rl_completion_matches ( text, complete_algorithm );
case CompletionContext::ALGORITHM_GROUP:
return rl_completion_matches ( text, complete_algorithm_group );
case CompletionContext::DATATYPE_GROUP:
return rl_completion_matches ( text, complete_datatype_group );
case CompletionContext::COMMAND:
return rl_completion_matches ( text, complete_command );
case CompletionContext::COMMAND_INTROSPECT:
return rl_completion_matches ( text, complete_command_introspect );
case CompletionContext::VARIABLE:
return rl_completion_matches ( text, complete_variable );
case CompletionContext::BINDING:
return rl_completion_matches ( text, complete_binding );
case CompletionContext::FILEPATH:
return rl_completion_matches ( text, complete_filepath );
case CompletionContext::FILEPATH_OR_VARIABLE:
return rl_completion_matches ( text, complete_filepath_or_variable );
case CompletionContext::SET:
return rl_completion_matches ( text, complete_set );
default:
return nullptr;
}
}
/*
* ReadlinePromptCompletion.h
*
* Created on: 22. 1. 2019
* Author: Tomas Pecka
*/
#ifndef _READLINE_PROMPT_COMPLETION_H
#define _READLINE_PROMPT_COMPLETION_H
#include <alib/vector>
#include <alib/set>
#include <alib/map>
#include <alib/string>
#include <registry/Registry.h>
#include <registry/XmlRegistry.h>
#include <environment/Environment.h>
#include <prompt/Prompt.h>
#include <readline/readline.h>
class ReadlinePromptCompletion {
static std::set < std::string > fetchAlgorithmsFullyQualifiedName ( const char *text ) {
std::set < std::string > fullyQualifiedNames;
for ( const ext::pair < std::string, ext::vector < std::string > > & algo : abstraction::Registry::listAlgorithms ( ) ) {
fullyQualifiedNames.insert ( algo.first );
}
return filter_completions ( fullyQualifiedNames, text );
}
static std::set < std::string > fetchAlgorithmsLastSegmentName ( const char *text ) {
std::map < std::string, unsigned > collisions;
for ( const ext::pair < std::string, ext::vector < std::string > > & algo : abstraction::Registry::listAlgorithms ( ) ) {
size_t pos = algo.first.find_last_of ( ':' );
if ( pos != std::string::npos )
collisions [ algo.first.substr ( pos + 1 ) ] += 1;
}
std::set < std::string > res;
for ( const std::pair < const std::string, unsigned > & kv : collisions )
if ( kv.second == 1 )
res.insert ( kv.first );
return filter_completions ( res, text );
}
static std::set < std::string > fetchAlgorithmGroups ( const char *text ) {
std::set < std::string > res;
for ( const ext::pair < std::string, ext::vector < std::string > > & algo : abstraction::Registry::listAlgorithms ( ) ) {
std::set < std::string > groups = getGroups ( algo.first );
res.insert ( groups.begin ( ), groups.end ( ) );
}
return filter_completions ( res, text );
}
static std::set < std::string > fetchDatatypeGroups ( const char *text ) {
std::set < std::string > res;
for ( const std::string & dtt : abstraction::XmlRegistry::listDataTypes ( ) ) {
std::set < std::string > groups = getGroups ( dtt );
res.insert ( groups.begin ( ), groups.end ( ) );
}
return filter_completions ( res, text );
}
static std::set < std::string > fetchCommands ( const char *text ) {
return filter_completions ( { "execute", "introspect", "quit", "help", "set" }, text );
}
static std::set < std::string > fetchCommandsIntrospect ( const char *text ) {
return filter_completions ( { "algorithms", "overloads", "casts", "datatypes", "variables", "bindings" }, text );
}
static std::set < std::string > fetchBindings ( const char *text ) {
return filter_completions ( addPrefix ( Prompt::getPrompt ( ).getEnvironment ( ).getBindingNames ( ), "#" ), text );
}
static std::set < std::string > fetchVariables ( const char *text ) {
return filter_completions ( addPrefix ( Prompt::getPrompt ( ).getEnvironment ( ).getVariableNames ( ), "$" ), text );
}
static std::set < std::string > fetchSet ( const char *text ) {
return filter_completions ( { "verbose", "measure", "optimizeXml", "seed" }, text );
}
static std::set < std::string > fetchFilepath ( const char *text ) {
std::set < std::string > res;
char *str;
int state = 0;
while ( ( str = rl_filename_completion_function ( text, state++ ) ) != nullptr ) {
res.insert ( str );
}
return res;
}
public:
enum class CompletionContext {
COMMAND,
COMMAND_INTROSPECT,
ALGORITHM,
ALGORITHM_GROUP,
DATATYPE_GROUP,
FILEPATH,
FILEPATH_OR_VARIABLE,
VARIABLE,
BINDING,
SET,
};
static char** readline_completion ( const char *text, int start, int end );
static CompletionContext context ( const char *text, int start, int end );
private:
static bool masterCommandCompletionTest ( const ext::string & line, int start, const std::string & expectation );
static std::set < std::string > addPrefix ( const std::set < std::string > & collection, const std::string & prefix );
static std::set < std::string > getGroups ( const std::string & qualified_name );
static std::set < std::string > filter_completions ( const std::set < std::string > & choices, const char *text );
/**
* @param text Prefix
* @param state Invocation number of this completion
* @param generator Function that generates the completion-strings
*/
template < typename... CompletionGeneratorFunc >
static char * completion_generator ( const char *text, int state, const CompletionGeneratorFunc & ... generators ) {
static std::string prefix;
static std::set < std::string > choices;
static std::set < std::string > :: const_iterator iter;
/* on first call initialize choices */
if ( state == 0 ) {
prefix = text;
choices = std::set < std::string > ( );
/* merge choices from all generators */
const std::vector < std::function < std::set < std::string > ( const char* ) > > gens = { generators... };
for ( const auto & gen : gens ) {
std::set < std::string > tmpres;
std::set < std::string > tmpg = gen ( text );
std::set_union ( std::begin ( choices ), std::end ( choices ),
std::begin ( tmpg ), std::end ( tmpg ),
std::inserter ( tmpres, std::begin ( tmpres ) ) );
choices = tmpres;
}
iter = choices.begin ( );
}
/* iterate through choices */
while ( iter != choices.end ( ) ) {
return strdup ( iter ++ -> c_str ( ) );
}
return nullptr;
}
/**
* http://www.delorie.com/gnu/docs/readline/rlman_45.html
* @param state Function invocation number
* @return malloc-allocated string or nullptr if no more strings can be generated.
*/
static char * complete_algorithm ( const char *text, int state ) {
return completion_generator ( text, state, fetchAlgorithmsFullyQualifiedName, fetchAlgorithmsLastSegmentName );
}
static char * complete_algorithm_group ( const char *text, int state ) {
return completion_generator ( text, state, fetchAlgorithmGroups );
}
static char * complete_datatype_group ( const char *text, int state ) {
return completion_generator ( text, state, fetchDatatypeGroups );
}
static char * complete_command ( const char *text, int state ) {
return completion_generator ( text, state, fetchCommands );
}
static char * complete_command_introspect ( const char *text, int state ) {
return completion_generator ( text, state, fetchCommandsIntrospect );
}
static char * complete_variable ( const char *text, int state ) {
return completion_generator ( text, state, fetchVariables );
}
static char * complete_binding ( const char *text, int state ) {
return completion_generator ( text, state, fetchBindings );
}
static char * complete_filepath ( const char *text, int state ) {
return completion_generator ( text, state, fetchFilepath );
}
static char * complete_filepath_or_variable ( const char *text, int state ) {
return completion_generator ( text, state, fetchFilepath, fetchVariables );
}
static char * complete_set ( const char *text, int state ) {
return completion_generator ( text, state, fetchSet );
}
};
#endif /* _READLINE_PROMPT_COMPLETION_H */
/*
* PromptCompletion.cpp
*
* Created on: 22. 1. 2019
* Author: Tomas Pecka
*/
#include <cstring>
#include "PromptCompletion.h"
#include "PromptCompletionContexts.h"
PromptCompletion::PromptCompletion ( const std::string & full_line, const std::string & completing_text, int start, int end )
: m_CompletingText ( completing_text), m_ContextManager ( full_line, completing_text, start, end ) {
}
ext::set < std::string > PromptCompletion::completion ( ) const {
ext::set < std::string > res;
for ( const std::unique_ptr < PromptCompletionContext > & ctx : m_ContextManager.getPossibleCompletionContexts ( ) ) {
ext::set < std::string > ctx_res = filter_completions ( ctx -> completion ( ) );
res.insert ( ctx_res.begin ( ), ctx_res.end ( ) );
}
return res;
}
// --------------------------------------------------------------------------------------------------------------------
// Helpers
ext::set < std::string > PromptCompletion::filter_completions ( const ext::set < std::string > & options ) const {
std::pair < ext::set < std::string > :: const_iterator, ext::set < std::string > :: const_iterator > range;
range = std::equal_range ( options.begin ( ), options.end ( ), m_CompletingText,
[ this ] ( const std::string & a, const std::string & b ) { return strncmp ( a.c_str ( ), b.c_str ( ), this -> m_CompletingText.size ( ) ) < 0; } );
return ext::set < std::string > ( range.first, range.second );
}
/*
* PromptCompletion.h
*
* Created on: 22. 1. 2019
* Author: Tomas Pecka
*/
#ifndef _PROMPT_COMPLETION_H
#define _PROMPT_COMPLETION_H
#include <alib/set>
#include "PromptCompletionContextManager.h"
/**
* Prompt completion. The completion is based on lexing the current input source.
*/
class PromptCompletion {
std::string m_CompletingText;
PromptCompletionContextManager m_ContextManager;
public:
/**
*
* @param full_line The full line
* @param completing_text The token that is currently being written
* @param start index to full_line
* @param end index to full_line
*/
PromptCompletion ( const std::string & full_line, const std::string & completing_text, int start, int end );
ext::set < std::string > completion ( ) const;
private:
/**
* Helper method that retains only possible completions.
*
* @param choices
* @return
*/
ext::set < std::string > filter_completions ( const ext::set < std::string > & options ) const;
};
#endif /* _READLINE_PROMPT_COMPLETION_H */
/*
* ReadlinePromptCompletionContext.cpp
*
* Created on: 26. 12. 2019
* Author: Tomas Pecka
*/
#include <readline/StringLineInterface.h>
#include <lexer/Lexer.h>
#include "PromptCompletionContextManager.h"
#include "PromptCompletionContexts.h"
PromptCompletionContextManager::PromptCompletionContextManager ( std::string line, std::string text, int start, int end ) :
full_line ( std::move ( line ) ), completing_text ( std::move ( text ) ), completing_start_idx ( start ), completing_end_idx ( end ) {
m_Tokens = lex ( );
( void ) completing_end_idx; // intentional, avoid unused member warning. I'd rather have this variable here, although unused now -- peckato1
}
ext::vector < std::unique_ptr < PromptCompletionContext > > PromptCompletionContextManager::getPossibleCompletionContexts ( ) const {
ext::vector < std::unique_ptr < PromptCompletionContext > > res;
// std::cerr << m_Tokens << std::endl;
if ( m_Tokens . empty ( ) || match ( cli::Lexer::TokenType::SEMICOLON_SIGN ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::COMMAND ) );
} else if ( match ( "introspect" ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::COMMAND_INTROSPECT ) );
} else if ( match ( "set" ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::COMMAND_SET ) );
} else if ( match ( "introspect", "overloads" ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::ALGORITHM ) );
} else if ( match ( "introspect", "variables" ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::VARIABLE ) );
} else if ( match ( "introspect", "bindings" ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::BINDING ) );
} else if ( match ( "introspect", "algorithms" ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::ALGORITHM_NAMESPACE ) );
} else if ( match ( "introspect", "datatypes" ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::DATATYPE_NAMESPACE ) );
} else if ( match ( "introspect", "casts" ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::DATATYPE_NAMESPACE ) );
} else if ( match ( cli::Lexer::TokenType::AT_SIGN ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::ALGORITHM_ARGUMENT, m_Tokens[ m_Tokens.size ( ) - 2 ] . m_value ) );
} else if ( match ( cli::Lexer::TokenType::DOLLAR_SIGN ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::VARIABLE ) );
} else if ( match ( cli::Lexer::TokenType::HASH_SIGN ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::BINDING ) );
} else if ( match ( cli::Lexer::TokenType::LESS_THAN ) || match ( cli::Lexer::TokenType::MORE_THAN ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::FILEPATH, completing_text ) );
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::VARIABLE ) );
} else if ( match ( cli::Lexer::TokenType::LESS_THAN, cli::Lexer::TokenType::LEFT_PAREN ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::FILEPATH, completing_text ) );
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::BINDING ) );
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::VARIABLE ) );
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::ALGORITHM ) );
} else if ( match ( cli::Lexer::TokenType::PIPE_SIGN ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::ALGORITHM ) );
} else if ( match ( "execute" ) || match ( "print" ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::ALGORITHM ) );
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::VARIABLE ) );
} else if ( match ( "execute", cli::Lexer::TokenType::IDENTIFIER ) || match ( "print", cli::Lexer::TokenType::IDENTIFIER ) ) {
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::ALGORITHM_ARGUMENT, m_Tokens[ m_Tokens.size ( ) - 1 ] . m_value ) );
} else { // undecided, fallback to filepath
res . push_back ( PromptCompletionContext::create ( PromptCompletionContextType::FILEPATH, completing_text ) );
}
return res;
}
ext::vector < cli::Lexer::Token > PromptCompletionContextManager::lex ( ) const {
// erase last (i.e. the "currently completing") token, keep only already completed context
/* general situation:
* > introspect over<TAB> automaton::
* - full line as above
* - completing text == "over"
* - start, end = indexes to the occurence of "over"
*
* This means: Lex only substring [0; start) of full_line
*/
std::string line_buffer = full_line . substr ( 0, completing_start_idx );
// lex tokens
cli::CharSequence sequence { cli::StringLineInterface ( line_buffer ) };
cli::Lexer lexer ( std::move ( sequence ) );
ext::vector < cli::Lexer::Token > tokens;
do {
tokens . push_back ( lexer . nextToken ( false ) );
} while ( tokens . back ( ) . m_type != cli::Lexer::TokenType::EOS && tokens . back ( ) . m_type != cli::Lexer::TokenType::EOT );
tokens . pop_back ( ); // erase EO*
return tokens;
}
template < class ... TokenOrString >
bool PromptCompletionContextManager::match ( TokenOrString ... rest ) const {
if ( m_Tokens.size ( ) - sizeof... ( rest ) < 0 )
return false;
return check ( m_Tokens.size ( ) - sizeof... ( rest ), rest ... );
}
bool PromptCompletionContextManager::check ( size_t token_idx, const std::string & first ) const {
if ( token_idx >= m_Tokens . size ( ) )
return false;
return m_Tokens[ token_idx ] . m_type == cli::Lexer::TokenType::IDENTIFIER && m_Tokens[ token_idx ] . m_value == first;
}
bool PromptCompletionContextManager::check ( size_t token_idx, const cli::Lexer::TokenType & first ) const {
if ( token_idx >= m_Tokens . size ( ) )
return false;
return m_Tokens[ token_idx ] . m_type == first;
}
template < class ... TokenOrString >
bool PromptCompletionContextManager::check ( size_t token_idx, const std::string & first, TokenOrString ... rest ) const {
if ( token_idx >= m_Tokens . size ( ) )
return false;
return m_Tokens[ token_idx ] . m_type == cli::Lexer::TokenType::IDENTIFIER && m_Tokens[ token_idx ] . m_value == first && check ( token_idx + 1, rest ... );
}
template < class ... TokenOrString >
bool PromptCompletionContextManager::check ( size_t token_idx, const cli::Lexer::TokenType & first, TokenOrString ... rest ) const {
if ( token_idx >= m_Tokens . size ( ) )
return false;
return m_Tokens[ token_idx ] . m_type == first && check ( token_idx + 1, rest ... );
}
/*
* ReadlinePromptCompletionContext.h
*
* Created on: 26. 12. 2019
* Author: Tomas Pecka
*/
#ifndef _READLINE_PROMPT_COMPLETION_CONTEXT_MANAGER_H
#define _READLINE_PROMPT_COMPLETION_CONTEXT_MANAGER_H
#include <alib/set>
#include <lexer/Lexer.h>
#include "PromptCompletionContexts.h"
/**
* Class responsible for lexing the input and determining the completion context
*/
class PromptCompletionContextManager {
std::string full_line, completing_text;
int completing_start_idx, completing_end_idx;
ext::vector < cli::Lexer::Token > m_Tokens;
/**
* Run lexer and return list of tokens on full_line
* @return
*/
ext::vector < cli::Lexer::Token > lex ( ) const;
/**
* Returns true if the passed list of tokens is present at the end of list of lexed tokens
*
* @tparam TokenOrString Either cli::Lexer::Token or std::string, which is considered to be an IdentifierType
* @param rest list of tokens to check
* @return
*/
template < class ... TokenOrString >
bool match ( TokenOrString ... rest ) const;
bool check ( size_t token_idx, const std::string & first ) const;
bool check ( size_t token_idx, const cli::Lexer::TokenType & first ) const;
template < class ... TokenOrString >
bool check ( size_t token_idx, const std::string & first, TokenOrString ... rest ) const;
template < class ... TokenOrString >
bool check ( size_t token_idx, const cli::Lexer::TokenType & first, TokenOrString ... rest ) const;
public:
PromptCompletionContextManager ( std::string line, std::string completingText, int start, int end );
/**
* Get list of current possible completion contexts
*
* @return vector of @see PromptCompletionContext
*/
ext::vector < std::unique_ptr < PromptCompletionContext > > getPossibleCompletionContexts ( ) const;
};
#endif /* _READLINE_PROMPT_COMPLETION_CONTEXT_MANAGER_H */
\ No newline at end of file
/*
* PromptCompletionContext.cpp
*
* Created on: 26. 12. 2019
* Author: Tomas Pecka
*/
#include <exception/CommonException.h>
#include "PromptCompletionContexts.h"
#include <readline/readline.h> // see todo lower
ext::set < std::string > PromptCompletionContext::getGroups ( const std::string & qualified_name ) {
ext::set < std::string > res;
unsigned template_level = 0;
for ( size_t i = 0 ; i < qualified_name . size ( ) ; i ++ ) {
if ( qualified_name[ i ] == '<' )