From f8c5c1a117b20984eab2e80d5943a21b42f67e01 Mon Sep 17 00:00:00 2001 From: tangxifan Date: Wed, 22 Jan 2020 16:05:14 -0700 Subject: [PATCH] update shell with new function binding strategy and new help desk print-out --- libopenfpga/libopenfpgashell/src/shell.h | 66 +++++++- libopenfpga/libopenfpgashell/src/shell.tpp | 150 +++++++++++++++++- libopenfpga/libopenfpgashell/src/shell_fwd.h | 2 + .../libopenfpgashell/test/test_shell.cpp | 59 ++++--- 4 files changed, 243 insertions(+), 34 deletions(-) diff --git a/libopenfpga/libopenfpgashell/src/shell.h b/libopenfpga/libopenfpgashell/src/shell.h index e1b910aef..2c64381a4 100644 --- a/libopenfpga/libopenfpgashell/src/shell.h +++ b/libopenfpga/libopenfpgashell/src/shell.h @@ -37,10 +37,11 @@ namespace minishell { * // Add execute function to the command 'read_arch' * // This is the function to be called when the command is used * // Assume that you have a function read_arch() which does the job of read architecture - * // Note that the function read_arch() should return a void with only two arguments - * // void read_arch(T, const CommandContext&); + * // Note that the function read_arch() should return a void with only three arguments + * // void read_arch(T, const Command&, const CommandContext&); * // The first argument is the template type you set when define the shell - * // The second argument is a read-only object storing the parsing results for the command + * // The second argument is a read-only object storing the options for the command + * // The third argument is a read-only object storing the parsing results for the command * shell.set_command_execute_function(cmd_read_arch, &read_arch); * * // Run a shell @@ -53,6 +54,15 @@ class Shell { typedef vtr::vector::const_iterator shell_command_iterator; /* Create range */ typedef vtr::Range shell_command_range; + /* Enumeration of command types in a shell + * Built-in commands have their own execute functions inside the shell + */ + enum e_exec_func_type { + STANDARD, + SHORT, + BUILTIN, + NUM_EXEC_FUNC_TYPES + }; public: /* Constructor */ Shell(const char* name); public: /* Public accessors */ @@ -61,26 +71,47 @@ class Shell { shell_command_range commands() const; ShellCommandId command(const std::string& name) const; std::string command_description(const ShellCommandId& cmd_id) const; + ShellCommandClassId command_class(const ShellCommandId& cmd_id) const; + std::string command_class_name(const ShellCommandClassId& cmd_class_id) const; /* We force a read-only return objects for command and command context * because users should NOT modify any of them! */ const Command& command(const ShellCommandId& cmd_id) const; const CommandContext& command_context(const ShellCommandId& cmd_id) const; std::vector command_dependency(const ShellCommandId& cmd_id) const; + std::vector commands_by_class(const ShellCommandClassId& cmd_class_id) const; public: /* Public mutators */ void add_title(const char* title); ShellCommandId add_command(const Command& cmd, const char* descr); + void set_command_class(const ShellCommandId& cmd_id, const ShellCommandClassId& cmd_class_id); + /* Link the execute function to a command + * We support here three types of functions to be executed in the shell + * 1. Standard function, including the data exchange and commands + * 2. Short function, including only the data exchange + * 3. Built-in function, including only the shell envoriment variables + * Users just need to specify the function object and its type will be automatically inferred + */ void set_command_execute_function(const ShellCommandId& cmd_id, std::function exec_func); + void set_command_execute_function(const ShellCommandId& cmd_id, + std::function exec_func); + void set_command_execute_function(const ShellCommandId& cmd_id, + std::function exec_func); void set_command_dependency(const ShellCommandId& cmd_id, const std::vector cmd_dependency); + ShellCommandClassId add_command_class(const char* name); public: /* Public validators */ bool valid_command_id(const ShellCommandId& cmd_id) const; + bool valid_command_class_id(const ShellCommandClassId& cmd_class_id) const; public: /* Public executors */ /* Start the interactive mode, where users will type-in command by command */ void run_interactive_mode(T& context); /* Start the script mode, where users provide a file which includes all the commands to run */ void run_script_mode(const char* script_file_name, T& context); + /* Print all the commands by their classes. This is actually the help desk */ + void print_commands() const; + /* Quit the shell */ + void exit() const; private: /* Private executors */ /* Execute a command, the command line is the user's input to launch a command * The common_context is the data structure to exchange data between commands @@ -93,6 +124,12 @@ class Shell { /* Title of the shell, this will appear in the interactive mode as an introduction */ std::string title_; + /* Unique ids for each class of command */ + vtr::vector command_class_ids_; + + /* Names for each class of command */ + vtr::vector command_class_names_; + /* Unique ids for each command */ vtr::vector command_ids_; @@ -105,8 +142,23 @@ class Shell { /* Description of the command, this is going to be printed out in the help desk */ vtr::vector command_description_; - /* Function pointers to execute each command */ - vtr::vector> command_execute_functions_; + /* Class ids for each command */ + vtr::vector command_classes_; + + /* Function pointers to execute each command + * We support here three types of functions to be executed in the shell + * 1. Standard function, including the data exchange and commands + * 2. Short function, including only the data exchange + * 3. Built-in function, including only the shell envoriment variables + */ + vtr::vector> command_standard_execute_functions_; + vtr::vector> command_short_execute_functions_; + vtr::vector> command_builtin_execute_functions_; + + /* Type of execute functions for each command. + * This is supposed to be an internal data ONLY + */ + vtr::vector command_execute_function_types_; /* Dependency graph for different commands, * This helps the shell interface to check if a command need other commands to be run before its execution @@ -115,11 +167,13 @@ class Shell { /* Fast name look-up */ std::map command_name2ids_; + std::map command_class2ids_; + vtr::vector> commands_by_classes_; }; } /* End namespace minshell */ -/* Include the implementation functions in the header file */ +/* Include the template implementation functions in the header file */ #include "shell.tpp" #endif diff --git a/libopenfpga/libopenfpgashell/src/shell.tpp b/libopenfpga/libopenfpgashell/src/shell.tpp index a989f5bc2..809c3c59a 100644 --- a/libopenfpga/libopenfpgashell/src/shell.tpp +++ b/libopenfpga/libopenfpgashell/src/shell.tpp @@ -2,6 +2,7 @@ * Member functions for class Shell ********************************************************************/ #include +#include /* Headers from vtrutil library */ #include "vtr_log.h" @@ -63,6 +64,18 @@ std::string Shell::command_description(const ShellCommandId& cmd_id) const { return command_description_[cmd_id]; } +template +ShellCommandClassId Shell::command_class(const ShellCommandId& cmd_id) const { + VTR_ASSERT(true == valid_command_id(cmd_id)); + return command_classes_[cmd_id]; +} + +template +std::string Shell::command_class_name(const ShellCommandClassId& cmd_class_id) const { + VTR_ASSERT(true == valid_command_class_id(cmd_class_id)); + return command_class_names_[cmd_class_id]; +} + template const Command& Shell::command(const ShellCommandId& cmd_id) const { VTR_ASSERT(true == valid_command_id(cmd_id)); @@ -81,6 +94,12 @@ std::vector Shell::command_dependency(const ShellCommandId& c return command_dependencies_[cmd_id]; } +template +std::vector Shell::commands_by_class(const ShellCommandClassId& cmd_class_id) const { + VTR_ASSERT(true == valid_command_class_id(cmd_class_id)); + return commands_by_classes_[cmd_class_id]; +} + /************************************************************************ * Public mutators ***********************************************************************/ @@ -104,7 +123,11 @@ ShellCommandId Shell::add_command(const Command& cmd, const char* descr) { commands_.emplace_back(cmd); command_contexts_.push_back(CommandContext(cmd)); command_description_.push_back(descr); - command_execute_functions_.emplace_back(); + command_classes_.push_back(ShellCommandClassId::INVALID()); + command_execute_function_types_.emplace_back(); + command_standard_execute_functions_.emplace_back(); + command_short_execute_functions_.emplace_back(); + command_builtin_execute_functions_.emplace_back(); command_dependencies_.emplace_back(); /* Register the name in the name2id map */ @@ -113,11 +136,41 @@ ShellCommandId Shell::add_command(const Command& cmd, const char* descr) { return shell_cmd; } +template +void Shell::set_command_class(const ShellCommandId& cmd_id, const ShellCommandClassId& cmd_class_id) { + VTR_ASSERT(true == valid_command_id(cmd_id)); + VTR_ASSERT(true == valid_command_class_id(cmd_class_id)); + command_classes_[cmd_id] = cmd_class_id; + /* Update the fast look-up to spot commands in a class */ + std::vector::iterator it = std::find(commands_by_classes_[cmd_class_id].begin(), commands_by_classes_[cmd_class_id].end(), cmd_id); + /* The command does not exist in the class, add it */ + if (it == commands_by_classes_[cmd_class_id].end()) { + commands_by_classes_[cmd_class_id].push_back(cmd_id); + } +} + template void Shell::set_command_execute_function(const ShellCommandId& cmd_id, std::function exec_func) { VTR_ASSERT(true == valid_command_id(cmd_id)); - command_execute_functions_[cmd_id] = exec_func; + command_execute_function_types_[cmd_id] = STANDARD; + command_standard_execute_functions_[cmd_id] = exec_func; +} + +template +void Shell::set_command_execute_function(const ShellCommandId& cmd_id, + std::function exec_func) { + VTR_ASSERT(true == valid_command_id(cmd_id)); + command_execute_function_types_[cmd_id] = SHORT; + command_short_execute_functions_[cmd_id] = exec_func; +} + +template +void Shell::set_command_execute_function(const ShellCommandId& cmd_id, + std::function exec_func) { + VTR_ASSERT(true == valid_command_id(cmd_id)); + command_execute_function_types_[cmd_id] = BUILTIN; + command_builtin_execute_functions_[cmd_id] = exec_func; } template @@ -131,6 +184,29 @@ void Shell::set_command_dependency(const ShellCommandId& cmd_id, command_dependencies_[cmd_id] = dependent_cmds; } +/* Add a command with it description */ +template +ShellCommandClassId Shell::add_command_class(const char* name) { + /* Ensure that the name is unique in the command list */ + std::map::const_iterator name_it = command_class2ids_.find(std::string(name)); + if (name_it != command_class2ids_.end()) { + return ShellCommandClassId::INVALID(); + } + + /* This is a legal name. we can create a new id */ + ShellCommandClassId cmd_class = ShellCommandClassId(command_class_ids_.size()); + command_class_ids_.push_back(cmd_class); + command_class_names_.push_back(std::string(name)); + + /* Register the name in the name2id map */ + command_class2ids_[std::string(name)] = cmd_class; + + /* Register in the fast look-up for commands by classes */ + commands_by_classes_.emplace_back(); + + return cmd_class; +} + /************************************************************************ * Public executors ***********************************************************************/ @@ -196,12 +272,51 @@ void Shell::run_script_mode(const char* script_file_name, T& context) { if (cmd_end_pos != std::string::npos) { cmd_part = line.substr(0, cmd_end_pos); } - /* Process the command */ - execute_command(cmd_part.c_str(), context); + /* Process the command only when the line is not empty */ + if (!cmd_part.empty()) { + execute_command(cmd_part.c_str(), context); + } } fp.close(); } +template +void Shell::print_commands() const { + /* Print the commands by their classes */ + for (const ShellCommandClassId& cmd_class : command_class_ids_) { + /* Print the class name */ + VTR_LOG("%s:\n", command_class_names_[cmd_class].c_str()); + + size_t cnt = 0; + for (const ShellCommandId& cmd : commands_by_classes_[cmd_class]) { + /* Print the command names in this class + * but limited4 command per line for a clean layout + */ + VTR_LOG("%s", commands_[cmd].name().c_str()); + cnt++; + if (4 == cnt) { + VTR_LOG("\n"); + cnt = 0; + } else { + VTR_LOG("\t"); + } + } + + /* Put a new line in the end as a splitter */ + VTR_LOG("\n"); + } + + /* Put a new line in the end as a splitter */ + VTR_LOG("\n"); +} + +template +void Shell::exit() const { + VTR_LOG("Thank you for using %s!\n", + name().c_str()); + std::exit(0); +} + /************************************************************************ * Private executors ***********************************************************************/ @@ -220,6 +335,8 @@ void Shell::execute_command(const char* cmd_line, return; } + /* TODO: Check the dependency graph to see if all the prequistics have been met */ + /* Find the command! Parse the options */ if (false == parse_command(tokens, commands_[cmd_id], command_contexts_[cmd_id])) { /* Echo the command */ @@ -230,8 +347,24 @@ void Shell::execute_command(const char* cmd_line, /* Parse succeed. Let user to confirm selected options */ print_command_context(commands_[cmd_id], command_contexts_[cmd_id]); - /* Execute the command! */ - command_execute_functions_[cmd_id](common_context, commands_[cmd_id], command_contexts_[cmd_id]); + /* Execute the command depending on the type of function ! */ + switch (command_execute_function_types_[cmd_id]) { + case STANDARD: + command_standard_execute_functions_[cmd_id](common_context, commands_[cmd_id], command_contexts_[cmd_id]); + break; + case SHORT: + command_short_execute_functions_[cmd_id](common_context); + break; + case BUILTIN: + command_builtin_execute_functions_[cmd_id](); + break; + default: + /* This is not allowed! Error out */ + VTR_LOG("Invalid type of execute function for command '%s'!\n", + commands_[cmd_id].name().c_str()); + /* Exit the shell using the exit() function inside this class! */ + exit(); + } } /************************************************************************ @@ -242,4 +375,9 @@ bool Shell::valid_command_id(const ShellCommandId& cmd_id) const { return ( size_t(cmd_id) < command_ids_.size() ) && ( cmd_id == command_ids_[cmd_id] ); } +template +bool Shell::valid_command_class_id(const ShellCommandClassId& cmd_class_id) const { + return ( size_t(cmd_class_id) < command_class_ids_.size() ) && ( cmd_class_id == command_class_ids_[cmd_class_id] ); +} + } /* End namespace minshell */ diff --git a/libopenfpga/libopenfpgashell/src/shell_fwd.h b/libopenfpga/libopenfpgashell/src/shell_fwd.h index 56a441644..cea5c70e2 100644 --- a/libopenfpga/libopenfpgashell/src/shell_fwd.h +++ b/libopenfpga/libopenfpgashell/src/shell_fwd.h @@ -10,8 +10,10 @@ namespace minishell { * A strong id for the options used by a command ********************************************************************/ struct shell_command_id_tag; +struct shell_command_class_id_tag; typedef vtr::StrongId ShellCommandId; +typedef vtr::StrongId ShellCommandClassId; } /* End namespace minshell */ diff --git a/libopenfpga/libopenfpgashell/test/test_shell.cpp b/libopenfpga/libopenfpgashell/test/test_shell.cpp index ee2d3fbd9..24099fc9b 100644 --- a/libopenfpga/libopenfpgashell/test/test_shell.cpp +++ b/libopenfpga/libopenfpgashell/test/test_shell.cpp @@ -12,23 +12,6 @@ using namespace minishell; class ShellContext { }; -static -void shell_cmd_help_executor(ShellContext& context, - const Command& cmd, - const CommandContext& cmd_context) { - VTR_LOG("Help desk:\n"); - VTR_LOG("Available commands:\n"); - VTR_LOG("help\texit\n"); -} - -static -void shell_cmd_exit_executor(ShellContext& context, - const Command& cmd, - const CommandContext& cmd_context) { - VTR_LOG("Thank you for using!\n"); - exit(1); -} - int main(int argc, char** argv) { /* Create the command to launch shell in different modes */ Command start_cmd("test_shell"); @@ -52,15 +35,45 @@ int main(int argc, char** argv) { * 2. exit */ Shell shell("test_shell"); - shell.add_title("This is a simple test shell\nAuthor: Xifan Tang\n"); + std::string shell_title; - Command shell_cmd_help("help"); - ShellCommandId shell_cmd_help_id = shell.add_command(shell_cmd_help, "Launch help desk"); - shell.set_command_execute_function(shell_cmd_help_id, shell_cmd_help_executor); + shell_title += std::string("The MIT License\n"); + shell_title += std::string("\n"); + shell_title += std::string("Copyright (c) 2018 LNIS - The University of Utah\n"); + shell_title += std::string("\n"); + shell_title += std::string("Permission is hereby granted, free of charge, to any person obtaining a copy\n"); + shell_title += std::string("of this software and associated documentation files (the \"Software\"), to deal\n"); + shell_title += std::string("in the Software without restriction, including without limitation the rights\n"); + shell_title += std::string("to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"); + shell_title += std::string("copies of the Software, and to permit persons to whom the Software is\n"); + shell_title += std::string("furnished to do so, subject to the following conditions:\n"); + shell_title += std::string("\n"); + shell_title += std::string("The above copyright notice and this permission notice shall be included in\n"); + shell_title += std::string("all copies or substantial portions of the Software.\n"); + shell_title += std::string("\n"); + shell_title += std::string("THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"); + shell_title += std::string("IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"); + shell_title += std::string("FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"); + shell_title += std::string("AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"); + shell_title += std::string("LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"); + shell_title += std::string("OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n"); + shell_title += std::string("THE SOFTWARE.\n"); + + shell.add_title(shell_title.c_str()); + + /* Add a new class of commands */ + ShellCommandClassId basic_cmd_class = shell.add_command_class("Basic"); Command shell_cmd_exit("exit"); ShellCommandId shell_cmd_exit_id = shell.add_command(shell_cmd_exit, "Exit the shell"); - shell.set_command_execute_function(shell_cmd_exit_id, shell_cmd_exit_executor); + shell.set_command_class(shell_cmd_exit_id, basic_cmd_class); + shell.set_command_execute_function(shell_cmd_exit_id, [shell](){shell.exit();}); + + /* Note: help must be the last to add because the linking to execute function will do a snapshot on the shell */ + Command shell_cmd_help("help"); + ShellCommandId shell_cmd_help_id = shell.add_command(shell_cmd_help, "Launch help desk"); + shell.set_command_class(shell_cmd_help_id, basic_cmd_class); + shell.set_command_execute_function(shell_cmd_help_id, [shell](){shell.print_commands();}); /* Create the data base for the shell */ ShellContext shell_context; @@ -88,6 +101,8 @@ int main(int argc, char** argv) { shell_context); return 0; } + /* Reach here there is something wrong, show the help desk */ + print_command_options(start_cmd); } return 0;