diff --git a/alib2cli/src/environment/Environment.h b/alib2cli/src/environment/Environment.h index 062d08bb22e8c7af62f5f621b37bfec15714216b..c5a590de233cda4131b0569f80e107d3761be3ff 100644 --- a/alib2cli/src/environment/Environment.h +++ b/alib2cli/src/environment/Environment.h @@ -55,6 +55,16 @@ public: throw exception::CommonException ( "Binded value of name " + name + " not found." ); } + const std::set < std::string > getBindingNames ( ) const { + std::set < std::string > res; + + for ( const std::pair < const std::string, std::string > & kv : m_bindings ) { + res.insert ( kv.first ); + } + + return res; + } + void setBinding ( std::string name, std::string value ) { m_bindings [ std::move ( name ) ] = std::move ( value ); } @@ -75,6 +85,16 @@ public: return ptr->getConstValueReference ( ); } + const std::set < std::string > getVariableNames ( ) const { + std::set < std::string > res; + + for ( const std::pair < const std::string, std::shared_ptr < abstraction::OperationAbstraction > > & kv : m_variables ) { + res.insert ( kv.first ); + } + + return res; + } + void setVariable ( std::string name, std::shared_ptr < abstraction::OperationAbstraction > value ) { setVariableInt ( std::move ( name ), std::move ( value ) ); } diff --git a/aql2/src/prompt/Prompt.cpp b/aql2/src/prompt/Prompt.cpp index 132e35203eb80dd7ecda30cdf4c0c18ac9b9d4db..db51f6919d4df5936f2f9527bff25fb4272a1c2f 100644 --- a/aql2/src/prompt/Prompt.cpp +++ b/aql2/src/prompt/Prompt.cpp @@ -6,6 +6,7 @@ */ #include "Prompt.h" +#include "ReadlinePromptCompletion.h" char esc_char [] = { '\a', '\b', '\f', '\n', '\r', '\t', '\v', '\\' }; char essc_str [] = { 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\' }; @@ -65,6 +66,11 @@ Prompt::Prompt ( cli::Environment environment ) : m_history_file ( std::string ( ++ history; ++ i; } + + + // register readline completion function, pass environment + rl_attempted_completion_function = readline_completion; + rl_aql_environment = & m_environment; } Prompt::~Prompt ( ) { diff --git a/aql2/src/prompt/ReadlinePromptCompletion.cpp b/aql2/src/prompt/ReadlinePromptCompletion.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4082d1f9c47898301214860d6cc0f74b6ae002b3 --- /dev/null +++ b/aql2/src/prompt/ReadlinePromptCompletion.cpp @@ -0,0 +1,232 @@ +/* + * ReadlinePromptCompletion.cpp + * + * Created on: 22. 1. 2019 + * Author: Tomas Pecka + */ + +#include <alib/set> +#include <alib/vector> +#include <alib/string> +#include <readline/readline.h> +#include <registry/Registry.h> + +#include "ReadlinePromptCompletion.h" + + +const cli::Environment* rl_aql_environment = nullptr; + +/* ========================================================================= */ + +std::set < std::string > 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; +} + +bool startswith ( const std::string & subject, const std::string & prefix ) { + if ( prefix.size ( ) > subject.size ( ) ) + return false; + + return subject.substr ( 0, prefix.size ( ) ) == prefix; +} + +std::set < std::string > 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 ); +} + +/* ========================================================================= */ + +std::set < std::string > fetchAlgorithms ( const char *text ) { + std::set < std::string > res; + const ext::set < ext::pair < std::string, ext::vector < std::string > > >& algos = abstraction::Registry::listAlgorithms ( ); + + for ( const std::pair < std::string, std::vector < std::string > > & algo : algos ) { + res.insert ( algo.first ); + } + + return filter_completions ( res, text ); +} + +std::set < std::string > fetchCommands ( const char *text ) { + return filter_completions ( { "execute", "introspect", "quit", "help" }, text ); +} + +std::set < std::string > fetchCommandsIntrospect ( const char *text ) { + return filter_completions ( { "algorithms", "overloads", "casts", "datatypes" }, text ); +} + +std::set < std::string > fetchBindings ( const char *text ) { + if ( rl_aql_environment ) + return filter_completions ( addPrefix ( rl_aql_environment -> getBindingNames ( ), "#" ), text ); + return std::set < std::string > ( ); +} + +std::set < std::string > fetchVariables ( const char *text ) { + if ( rl_aql_environment ) + return filter_completions ( addPrefix ( rl_aql_environment -> getVariableNames ( ), "$" ), text ); + return std::set < std::string > ( ); +} + +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; +} + +/* ========================================================================= */ + +/** + * @param text Prefix + * @param state Invocation number of this completion + * @param generator Function that generates the completion-strings + */ +template < typename... CompletionGeneratorFunc > +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, 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 NULL if no more strings can be generated. + * */ +char * complete_algorithm ( const char *text, int state ) { + return completion_generator ( text, state, fetchAlgorithms ); +} + +char * complete_command ( const char *text, int state ) { + return completion_generator ( text, state, fetchCommands ); +} + +char * complete_command_introspect ( const char *text, int state ) { + return completion_generator ( text, state, fetchCommandsIntrospect ); +} + +char * complete_variable ( const char *text, int state ) { + return completion_generator ( text, state, fetchVariables ); +} + +char * complete_binding ( const char *text, int state ) { + return completion_generator ( text, state, fetchBindings ); +} + +char * complete_filepath ( const char *text, int state ) { + return completion_generator ( text, state, fetchFilepath ); +} + +char * complete_filepath_or_variable ( const char *text, int state ) { + return completion_generator ( text, state, fetchFilepath, fetchVariables ); +} + +/* ========================================================================= */ + +CompletionContext context ( const char *text, const int start, const int end ) { + std::string line ( rl_line_buffer ); + + if ( start == 0 ) + return CompletionContext::COMMAND; + + if ( startswith ( line, "introspect" ) && start == 11 ) { + return CompletionContext::COMMAND_INTROSPECT; + } + else if ( startswith ( line, "introspect overloads" ) && start == 21 ) + return CompletionContext::ALGORITHM; + + /* TODO + else if ( startswith ( line, "introspect algorithms" ) && start == 21 ) + else if ( startswith ( line, "introspect casts" ) && start == 17 ) + else if ( startswith ( line, "introspect datatypes" ) && start == 20 ) + */ + + 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 && isspace ( *p ) ) p --; + if ( p >= rl_line_buffer && ( *p == '<' || *p == '>' ) ) + return CompletionContext::FILEPATH_OR_VARIABLE; + + if ( startswith ( line, "execute" ) && start == 8 ) + return CompletionContext::ALGORITHM; + + /* undecided, fallback to filepath */ + return CompletionContext::FILEPATH; +} + +/* ========================================================================= */ + +char ** 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::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 ); + default: + return nullptr; + } +} diff --git a/aql2/src/prompt/ReadlinePromptCompletion.h b/aql2/src/prompt/ReadlinePromptCompletion.h new file mode 100644 index 0000000000000000000000000000000000000000..dc6105b027cf2b6c080bf704cd5c05b0827c9fa6 --- /dev/null +++ b/aql2/src/prompt/ReadlinePromptCompletion.h @@ -0,0 +1,35 @@ +/* + * 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 <readline/readline.h> +#include <environment/Environment.h> + +extern const cli::Environment* rl_aql_environment; + +enum class CompletionContext { + COMMAND, + COMMAND_INTROSPECT, + + ALGORITHM, + + FILEPATH, + FILEPATH_OR_VARIABLE, + + VARIABLE, + BINDING, +}; + +char** readline_completion ( const char *text, int start, int end ); + +#endif /* _READLINE_PROMPT_COMPLETION_H */