diff --git a/libopenfpga/CMakeLists.txt b/libopenfpga/CMakeLists.txt index 2040b5225..9fe07ea19 100644 --- a/libopenfpga/CMakeLists.txt +++ b/libopenfpga/CMakeLists.txt @@ -1,4 +1,5 @@ # OpenFPGA-related libraries add_subdirectory(libini) +add_subdirectory(libminishell) add_subdirectory(libarchopenfpga) add_subdirectory(libopenfpgautil) diff --git a/libopenfpga/libminishell/CMakeLists.txt b/libopenfpga/libminishell/CMakeLists.txt new file mode 100644 index 000000000..66a037e35 --- /dev/null +++ b/libopenfpga/libminishell/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.9) + +project("libminishell") + +#file(GLOB_RECURSE EXEC_SOURCES test/main.cpp) +file(GLOB_RECURSE LIB_SOURCES src/*.cpp) +file(GLOB_RECURSE LIB_HEADERS src/*.h) +files_to_dirs(LIB_HEADERS LIB_INCLUDE_DIRS) + +#Remove test executable from library +#list(REMOVE_ITEM LIB_SOURCES ${EXEC_SOURCES}) + +#Create the library +add_library(libminishell STATIC + ${LIB_HEADERS} + ${LIB_SOURCES}) +target_include_directories(libminishell PUBLIC ${LIB_INCLUDE_DIRS}) +set_target_properties(libminishell PROPERTIES PREFIX "") #Avoid extra 'lib' prefix + +#Specify link-time dependancies +target_link_libraries(libminishell + libvtrutil) + +#Create the test executable +#add_executable(read_arch_openfpga ${EXEC_SOURCES}) +#target_link_libraries(read_arch_openfpga libarchopenfpga) + +#Supress IPO link warnings if IPO is enabled +#get_target_property(READ_ARCH_USES_IPO read_arch_openfpga INTERPROCEDURAL_OPTIMIZATION) +#if (READ_ARCH_USES_IPO) +# set_target_properties(read_arch_openfpga PROPERTIES LINK_FLAGS ${IPO_LINK_WARN_SUPRESS_FLAGS}) +#endif() + +#install(TARGETS libarchopenfpga read_arch_openfpga DESTINATION bin) diff --git a/libopenfpga/libminishell/src/command.cpp b/libopenfpga/libminishell/src/command.cpp new file mode 100644 index 000000000..d1bd54329 --- /dev/null +++ b/libopenfpga/libminishell/src/command.cpp @@ -0,0 +1,172 @@ +/********************************************************************* + * Member functions for class Command + ********************************************************************/ +#include "vtr_assert.h" +#include "command.h" + +/* Begin namespace minishell */ +namespace minishell { + +/********************************************************************* + * Public constructors + ********************************************************************/ +Command::Command(const std::string& name) { + name_ = name; +} + +/************************************************************************ + * Public Accessors : aggregates + ***********************************************************************/ +std::string Command::name() const { + return name_; +} + +Command::command_option_range Command::options() const { + return vtr::make_range(option_ids_.begin(), option_ids_.end()); +} + +std::vector Command::required_options() const { + std::vector opts; + for (const CommandOptionId& opt : options()) { + if (true == option_required(opt)) { + opts.push_back(opt); + } + } + return opts; +} + +std::vector Command::require_value_options() const { + std::vector opts; + for (const CommandOptionId& opt : options()) { + if (true == option_require_value(opt)) { + opts.push_back(opt); + } + } + return opts; +} + +CommandOptionId Command::option(const std::string& name) const { + /* Ensure that the name is unique in the option list */ + std::map::const_iterator name_it = option_name2ids_.find(name); + if (name_it != option_name2ids_.end()) { + return CommandOptionId::INVALID(); + } + return option_name2ids_.at(name); +} + +CommandOptionId Command::short_option(const std::string& name) const { + /* Ensure that the name is unique in the option list */ + std::map::const_iterator name_it = option_short_name2ids_.find(name); + if (name_it != option_short_name2ids_.end()) { + return CommandOptionId::INVALID(); + } + return option_short_name2ids_.at(name); +} + +std::string Command::option_name(const CommandOptionId& option_id) const { + /* Validate the option id */ + VTR_ASSERT(true == valid_option_id(option_id)); + return option_names_[option_id]; +} + +std::string Command::option_short_name(const CommandOptionId& option_id) const { + /* Validate the option id */ + VTR_ASSERT(true == valid_option_id(option_id)); + return option_short_names_[option_id]; +} + +bool Command::option_required(const CommandOptionId& option_id) const { + /* Validate the option id */ + VTR_ASSERT(true == valid_option_id(option_id)); + return option_required_[option_id]; +} + +bool Command::option_require_value(const CommandOptionId& option_id) const { + /* Validate the option id */ + VTR_ASSERT(true == valid_option_id(option_id)); + return NUM_OPT_VALUE_TYPES != option_require_value_types_[option_id]; +} + +e_option_value_type Command::option_require_value_type(const CommandOptionId& option_id) const { + /* Validate the option id */ + VTR_ASSERT(true == valid_option_id(option_id)); + return option_require_value_types_[option_id]; +} + +std::string Command::option_description(const CommandOptionId& option_id) const { + /* Validate the option id */ + VTR_ASSERT(true == valid_option_id(option_id)); + return option_description_[option_id]; +} + +/************************************************************************ + * Public mutators + ***********************************************************************/ + +/* Add an option without required values */ +CommandOptionId Command::add_option(const std::string& name, + const bool& option_required, + const std::string& description) { + /* Ensure that the name is unique in the option list */ + std::map::const_iterator name_it = option_name2ids_.find(name); + if (name_it != option_name2ids_.end()) { + return CommandOptionId::INVALID(); + } + + /* This is a legal name. we can create a new id */ + CommandOptionId option = CommandOptionId(option_ids_.size()); + option_ids_.push_back(option); + option_names_.push_back(name); + option_short_names_.emplace_back(); + option_required_.push_back(option_required); + option_require_value_types_.push_back(NUM_OPT_VALUE_TYPES); + option_description_.push_back(description); + + /* Register the name and short name in the name2id map */ + option_name2ids_[name] = option; + + return option; +} + +/* Add a short name to an option */ +bool Command::set_option_short_name(const CommandOptionId& option_id, + const std::string& short_name) { + /* Validate the option id */ + VTR_ASSERT(true == valid_option_id(option_id)); + + /* Short name is optional, so do the following only when it is not empty + * Ensure that the short name is unique in the option list + */ + if (true == short_name.empty()) { + return false; + } + + std::map::const_iterator short_name_it = option_short_name2ids_.find(short_name); + if (short_name_it != option_short_name2ids_.end()) { + return false; + } + + option_short_names_[option_id] = short_name; + + /* Short name is optional, so register it only when it is not empty */ + option_short_name2ids_[short_name] = option_id; + + return true; +} + +void Command::set_option_require_value(const CommandOptionId& option_id, + const e_option_value_type& option_require_value_type) { + /* Validate the option id */ + VTR_ASSERT(true == valid_option_id(option_id)); + + option_require_value_types_[option_id] = option_require_value_type; +} + +/************************************************************************ + * Public invalidators/validators + ***********************************************************************/ +bool Command::valid_option_id(const CommandOptionId& option_id) const { + return ( size_t(option_id) < option_ids_.size() ) && ( option_id == option_ids_[option_id] ); +} + +} /* End namespace minshell */ diff --git a/libopenfpga/libminishell/src/command.h b/libopenfpga/libminishell/src/command.h new file mode 100644 index 000000000..fe9bbb84b --- /dev/null +++ b/libopenfpga/libminishell/src/command.h @@ -0,0 +1,111 @@ +#ifndef COMMAND_H +#define COMMAND_H + +/********************************************************************* + * This header file includes data structure for option reading + * This is a cornerstone data structure of mini shell + * which aims to store command-line options for parser + * + * This data structue is design to read-in the command line options + * which are organized as follows: + * + * -- + * -- ... + * + * This is not only used by each command available in the shell + * but also the interface of the shell, such as interactive mode + ********************************************************************/ +#include +#include +#include + +#include "vtr_vector.h" +#include "vtr_range.h" +#include "command_fwd.h" + +/* Begin namespace minishell */ +namespace minishell { + +/********************************************************************* + * Supported date types of value which is followed by a command-line option + ********************************************************************/ +enum e_option_value_type { + OPT_INT, + OPT_FLOAT, + OPT_STRING, + NUM_OPT_VALUE_TYPES +}; + +/********************************************************************* + * Data structure to stores all the information related to a command + * to be defined in the mini shell + * This data structure should NOT contain any parsing results! + * It should be a read-only once created! + * + * Note: + * When adding an option name, please do NOT add any dash at the beginning!!! + ********************************************************************/ +class Command { + public: /* Types */ + typedef vtr::vector::const_iterator command_option_iterator; + /* Create range */ + typedef vtr::Range command_option_range; + public: /* Constructor */ + Command(const std::string& name); + public: /* Public accessors */ + std::string name() const; + command_option_range options() const; + /* Find all the options that are mandatory */ + std::vector required_options() const; + /* Find all the options that require a value */ + std::vector require_value_options() const; + CommandOptionId option(const std::string& name) const; + CommandOptionId short_option(const std::string& name) const; + std::string option_name(const CommandOptionId& option_id) const; + std::string option_short_name(const CommandOptionId& option_id) const; + bool option_required(const CommandOptionId& option_id) const; + bool option_require_value(const CommandOptionId& option_id) const; + e_option_value_type option_require_value_type(const CommandOptionId& option_id) const; + std::string option_description(const CommandOptionId& option_id) const; + public: /* Public mutators */ + CommandOptionId add_option(const std::string& name, + const bool& option_required, + const std::string& description); + bool set_option_short_name(const CommandOptionId& option_id, const std::string& short_name); + void set_option_require_value(const CommandOptionId& option_id, + const e_option_value_type& option_require_value_type); + public: /* Public validators */ + bool valid_option_id(const CommandOptionId& option_id) const; + private: /* Internal data */ + /* The name of the command */ + std::string name_; + + vtr::vector option_ids_; + + /* Information about the options available in this command */ + /* Name of command line option to appear in the user interface + * Regular names are typically with a prefix of double dash '--' + * Short names are typically with a prefix of single dash '-' + */ + vtr::vector option_names_; + vtr::vector option_short_names_; + + /* If the option is manadatory when parsing */ + vtr::vector option_required_; + + /* Data type of the option values to be converted + * If the option does NOT require a value, this will be an invalid type + */ + vtr::vector option_require_value_types_; + + /* Description of the option, this is going to be printed out in the help desk */ + vtr::vector option_description_; + + /* Fast name look-up */ + std::map option_name2ids_; + std::map option_short_name2ids_; +}; + +} /* End namespace minshell */ + +#endif diff --git a/libopenfpga/libminishell/src/command_context.cpp b/libopenfpga/libminishell/src/command_context.cpp new file mode 100644 index 000000000..30f9992ad --- /dev/null +++ b/libopenfpga/libminishell/src/command_context.cpp @@ -0,0 +1,69 @@ +/********************************************************************* + * Member functions for class CommandContext + ********************************************************************/ +#include "vtr_assert.h" +#include "command_context.h" + +/* Begin namespace minishell */ +namespace minishell { + +/********************************************************************* + * Public constructor + ********************************************************************/ +CommandContext::CommandContext(const Command& command) { + option_enabled_.resize(command.options().size(), false); + option_values_.resize(command.options().size()); +} + +/********************************************************************* + * Public accessors + ********************************************************************/ +bool CommandContext::option_enable(const Command& command, + const CommandOptionId& option_id) const { + VTR_ASSERT(true == command.valid_option_id(option_id)); + return option_enabled_[option_id]; +} + +std::string CommandContext::option_value(const Command& command, + const CommandOptionId& option_id) const { + VTR_ASSERT(true == command.valid_option_id(option_id)); + return option_values_[option_id]; +} + +std::vector CommandContext::check_required_options(const Command& command) const { + std::vector fail_options; + for (const CommandOptionId& option_id : command.required_options()) { + if (false == option_enabled_[option_id]) { + fail_options.push_back(option_id); + } + } + return fail_options; +} + +std::vector CommandContext::check_required_option_values(const Command& command) const { + std::vector fail_options; + for (const CommandOptionId& option_id : command.require_value_options()) { + if (true == option_values_[option_id].empty()) { + fail_options.push_back(option_id); + } + } + return fail_options; +} + + +/********************************************************************* + * Public mutators + ********************************************************************/ +void CommandContext::set_option(const Command& command, + const CommandOptionId& option_id, const bool& status) { + VTR_ASSERT(true == command.valid_option_id(option_id)); + option_enabled_[option_id] = status; +} + +void CommandContext::set_option_value(const Command& command, + const CommandOptionId& option_id, const std::string& value) { + VTR_ASSERT(true == command.valid_option_id(option_id)); + option_values_[option_id] = value; +} + +} /* End namespace minshell */ diff --git a/libopenfpga/libminishell/src/command_context.h b/libopenfpga/libminishell/src/command_context.h new file mode 100644 index 000000000..fb983218e --- /dev/null +++ b/libopenfpga/libminishell/src/command_context.h @@ -0,0 +1,59 @@ +#ifndef COMMAND_CONTEXT_H +#define COMMAND_CONTEXT_H + +#include +#include "vtr_vector.h" +#include "command_fwd.h" +#include "command.h" + +/* Begin namespace minishell */ +namespace minishell { + +/********************************************************************* + * Data structure to stores parsing results for a command + ********************************************************************/ +class CommandContext { + public: /* Public constructor */ + /* Build a context based on the command information + * This will allocate the internal data structure and assign default values + */ + CommandContext(const Command& command); + public: /* Public accessors */ + bool option_enable(const Command& command, + const CommandOptionId& option_id) const; + std::string option_value(const Command& command, + const CommandOptionId& option_id) const; + /* Check if all the required options have been enabled + * Any thing wrong will be reported in the return vector + * + * Note: + * This function is supposed to be called after parsing is completed + */ + std::vector check_required_options(const Command& command) const; + + /* Check all the values that are required by the options + * Any thing wrong will be reported in the return vector + * + * Note: + * This function is supposed to be called after parsing is completed + */ + std::vector check_required_option_values(const Command& command) const; + public: /* Public mutators */ + void set_option(const Command& command, + const CommandOptionId& option_id, const bool& status); + void set_option_value(const Command& command, + const CommandOptionId& option_id, const std::string& value); + private: /* Internal data */ + /* Identify if the option is enabled or not */ + vtr::vector option_enabled_; + + /* All the follow-up value of each option is stored as string, + * It will be converted the data types by outside function + * according to the value types + */ + vtr::vector option_values_; +}; + +} /* End namespace minshell */ + +#endif diff --git a/libopenfpga/libminishell/src/command_fwd.h b/libopenfpga/libminishell/src/command_fwd.h new file mode 100644 index 000000000..52dd81d65 --- /dev/null +++ b/libopenfpga/libminishell/src/command_fwd.h @@ -0,0 +1,18 @@ +#ifndef COMMAND_FWD_H +#define COMMAND_FWD_H + +#include "vtr_strong_id.h" + +/* Begin namespace minishell */ +namespace minishell { + +/********************************************************************* + * A strong id for the options used by a command + ********************************************************************/ +struct command_option_id_tag; + +typedef vtr::StrongId CommandOptionId; + +} /* End namespace minshell */ + +#endif diff --git a/libopenfpga/libminishell/src/command_parser.cpp b/libopenfpga/libminishell/src/command_parser.cpp new file mode 100644 index 000000000..4f17dbfbe --- /dev/null +++ b/libopenfpga/libminishell/src/command_parser.cpp @@ -0,0 +1,157 @@ +/******************************************************************** + * This file includes parser(s) to convert user's input from + * shell interface to a data structure CommandContext + * + * TODO: a strong dependency is that we use VTR logging system + *******************************************************************/ +#include + +#include "vtr_assert.h" +#include "vtr_log.h" +#include "command_parser.h" + +/* Begin namespace minishell */ +namespace minishell { + +/******************************************************************** + * Try to find an option in the command and update the CommandContext if needed + *******************************************************************/ +static +CommandOptionId parse_option(const std::string& argv, + const Command& cmd, + CommandContext& cmd_context) { + CommandOptionId option_id = cmd.option(argv); + /* Not found, error out */ + if (CommandOptionId::INVALID() == option_id) { + VTR_LOG("Detect unknown option '%s'!\n", + argv.c_str()); + } + /* Found, update the CommandContext */ + cmd_context.set_option(cmd, option_id, true); + + return option_id; +} + +/******************************************************************** + * Try to find a short option in the command + * Update the CommandContext if needed + *******************************************************************/ +static +CommandOptionId parse_short_option(const std::string& argv, + const Command& cmd, + CommandContext& cmd_context) { + CommandOptionId option_id = cmd.short_option(argv); + /* Not found, error out */ + if (CommandOptionId::INVALID() == option_id) { + VTR_LOG("Detect unknown option '%s'!\n", + argv.c_str()); + } + /* Found, update the CommandContext */ + cmd_context.set_option(cmd, option_id, true); + + return option_id; +} + +/******************************************************************** + * Main parser to convert user's input from + * shell interface to a data structure CommandContext + *******************************************************************/ +bool parse_command(const std::vector& argv, + const Command& cmd, + CommandContext& cmd_context) { + /* We at least expect 1 arguement, which is the command name itself */ + VTR_ASSERT(1 <= argv.size()); + + /* Validate that the command name matches argv[0] */ + if (argv[0] == cmd.name()) { + VTR_LOG("Unexpected command name '%s'!", + argv[0]); + return false; + } + + /* Start from argv[1], the 1st argv is programme name */ + for (size_t iarg = 1; iarg < argv.size(); ++iarg) { + /* Option must start with dash */ + if (0 != strncmp("-", argv[iarg].c_str(), 1)) { + VTR_LOG("Invalid option '%s'!", + argv[iarg].c_str()); + return false; + } + /* First try to process a full option + * which always starts with double dash '--' + */ + if (0 == strncmp("--", argv[iarg].c_str(), 2)) { + /* See if there is a option defined in the command object + * Note that the first two characters are skipped when searching the name + */ + CommandOptionId option_id = parse_option(argv[iarg].substr(2), cmd, cmd_context); + + if (CommandOptionId::INVALID() == option_id) { + return false; + } + + /* If the option requires a value, we visit the next argument */ + if (true == cmd.option_require_value(option_id)) { + ++iarg; + /* If this is the last arugment, we finish */ + if (iarg == argv.size()) { + break; + } + cmd_context.set_option_value(cmd, option_id, argv[iarg]); + } + /* Finish this iteration here, we found something */ + continue; + } + + /* Second try to process a short option + * which always starts with double dash '-' + */ + if (0 == strncmp("-", argv[iarg].c_str(), 1)) { + /* See if there is a option defined in the command object + * Note that the first two characters are skipped when searching the name + */ + CommandOptionId option_id = parse_short_option(argv[iarg].substr(1), cmd, cmd_context); + + if (CommandOptionId::INVALID() == option_id) { + return false; + } + + /* If the option requires a value, we visit the next argument */ + if (true == cmd.option_require_value(option_id)) { + ++iarg; + /* If this is the last arugment, we finish */ + if (iarg == argv.size()) { + break; + } + cmd_context.set_option_value(cmd, option_id, argv[iarg]); + } + /* Finish this iteration here, we found something */ + continue; + } + } + + /* Ensure that all the required options have been satisfied + * If not, we echo the details about what are missing + */ + std::vector missing_options = cmd_context.check_required_options(cmd); + if (!missing_options.empty()) { + for (const CommandOptionId& missing_opt : missing_options) { + VTR_LOG("Required option '%s' is missing!", + cmd.option_name(missing_opt)); + } + return false; + } + + std::vector missing_value_options = cmd_context.check_required_option_values(cmd); + if (!missing_value_options.empty()) { + for (const CommandOptionId& missing_opt : missing_value_options) { + VTR_LOG("Require value for option '%s' is missing!", + cmd.option_name(missing_opt)); + } + return false; + } + + return true; +} + +} /* End namespace minshell */ diff --git a/libopenfpga/libminishell/src/command_parser.h b/libopenfpga/libminishell/src/command_parser.h new file mode 100644 index 000000000..3ffb76423 --- /dev/null +++ b/libopenfpga/libminishell/src/command_parser.h @@ -0,0 +1,17 @@ +#ifndef COMMAND_PARSER_H +#define COMMAND_PARSER_H + +#include +#include "command.h" +#include "command_context.h" + +/* Begin namespace minishell */ +namespace minishell { + +bool parse_command(const std::vector& argv, + const Command& cmd, + CommandContext& cmd_context); + +} /* End namespace minshell */ + +#endif