/* Please use git log for copyright holder and year information This file is part of libbash. libbash is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. libbash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with libbash. If not, see . */ /// /// \file interpreter.cpp /// \brief implementations for bash interpreter (visitor pattern). /// #include "core/interpreter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core/bash_ast.h" namespace { std::string get_options(std::map& options) { std::string result; boost::copy(options | boost::adaptors::map_keys | boost::adaptors::filtered( [&](char option) -> bool { return options[option]; } ), back_inserter(result)); return result; } } interpreter::interpreter(): _out(&std::cout), _err(&std::cerr), _in(&std::cin), additional_options( { {"autocd", false}, {"cdable_vars", false}, {"cdspell", false}, {"checkhash", false}, {"checkjobs", false}, {"checkwinsize", false}, {"cmdhist", false}, {"compat31", false}, {"compat32", false}, {"compat40", false}, {"dirspell", false}, {"dotglob", false}, {"execfail", false}, {"expand_aliases", false}, {"extdebug", false}, {"extglob", false}, {"extquote", true}, {"failglob", false}, {"force_fignore", false}, {"globstar", false}, {"gnu_errfmt", false}, {"histappend", false}, {"histreedit", false}, {"histverify", false}, {"hostcomplete", false}, {"huponexit", false}, {"interactive_comments", false}, {"lithist", false}, {"login_shell", false}, {"mailwarn", false}, {"no_empty_cmd_completion", false}, {"nocaseglob", false}, {"nocasematch", false}, {"nullglob", false}, {"progcomp", false}, {"promptvars", false}, {"restricted_shell", false}, {"shift_verbose", false}, {"sourcepath", false}, {"xpg_echo", false}, } ), options( { {'a', false}, {'b', false}, {'e', false}, {'f', false}, {'h', true}, {'k', false}, {'m', false}, {'n', false}, {'p', false}, {'t', false}, {'u', false}, {'v', false}, {'x', false}, {'B', true}, {'C', false}, {'E', false}, {'H', false}, {'P', false}, {'T', false}, } ), status(0) { define("IFS", " \t\n"); // We do not support the options set by the shell itself (such as the -i option) define("-", get_options(options)); } std::shared_ptr interpreter::resolve_variable(const std::string& name) const { if(name.empty()) return std::shared_ptr(); BOOST_REVERSE_FOREACH(auto& frame, local_members) { auto iter_local = frame.find(name); if(iter_local != frame.end()) return iter_local->second; } auto iter_global = members.find(name); if(iter_global == members.end()) return std::shared_ptr(); return iter_global->second; } bool interpreter::is_unset_or_null(const std::string& name, const unsigned index) const { auto var = resolve_variable(name); if(!var) return true; return var->is_null(index); } std::string interpreter::get_substring(const std::string& name, long long offset, unsigned length, const unsigned index) const { std::string value = resolve(name, index); if(!get_real_offset(offset, boost::numeric_cast(value.size()))) return ""; // After get_real_offset, we know offset can be cast to unsigned. return value.substr(boost::numeric_cast(offset), length); } const std::string interpreter::do_substring_expansion(const std::string& name, long long offset, const unsigned index) const { return get_substring(name, offset, std::numeric_limits::max(), index); } const std::string interpreter::do_substring_expansion(const std::string& name, long long offset, int length, const unsigned index) const { if(length < 0) throw libbash::illegal_argument_exception("length of substring expression should be greater or equal to zero"); return get_substring(name, offset, boost::numeric_cast(length), index); } std::string interpreter::get_subarray(const std::string& name, long long offset, unsigned length) const { std::vector array; if(name == "*" || name == "@") { // ${*:1} has the same content as ${*}, ${*:0} contains current script name as the first element if(offset > 0) offset--; else if(offset == 0) array.push_back(resolve("0")); } // We do not support arrays that have size bigger than numeric_limits::max() if(resolve_array(name, array) && get_real_offset(offset, boost::numeric_cast(array.size()))) { // We do not support arrays that have size bigger than numeric_limits::max() // After get_real_offset, we know offset can be cast to unsigned. unsigned max_length = boost::numeric_cast(array.size()) - boost::numeric_cast(offset); if(length > max_length) length = max_length; auto start = array.begin() + boost::numeric_cast::difference_type>(offset); auto end = array.begin() + boost::numeric_cast::difference_type>(offset + length); return boost::algorithm::join(std::vector(start, end), resolve("IFS").substr(0, 1)); } else { return ""; } } const std::string interpreter::do_subarray_expansion(const std::string& name, long long offset) const { return get_subarray(name, offset, std::numeric_limits::max()); } const std::string interpreter::do_subarray_expansion(const std::string& name, long long offset, int length) const { if(length < 0) throw libbash::illegal_argument_exception("length of substring expression should be greater or equal to zero"); return get_subarray(name, offset, boost::numeric_cast(length)); } std::string interpreter::do_replace_expansion(const std::string& name, std::function replacer, const unsigned index) const { std::string value = resolve(name, index); replacer(value); return value; } std::string::size_type interpreter::get_length(const std::string& name, const unsigned index) const { auto var = resolve_variable(name); if(!var) return 0; return var->get_length(index); } variable::size_type interpreter::get_array_length(const std::string& name) const { auto var = resolve_variable(name); if(!var) return 0; return var->get_array_length(); } void interpreter::get_all_elements_joined(const std::string& name, const std::string& delim, std::string& result) const { std::vector array; if(resolve_array(name, array)) result = boost::algorithm::join(array, delim); else result = ""; } void interpreter::get_all_elements(const std::string& name, std::string& result) const { get_all_elements_joined(name, " ", result); } void interpreter::get_all_elements_IFS_joined(const std::string& name, std::string& result) const { get_all_elements_joined(name, resolve("IFS").substr(0, 1), result); } void interpreter::split_word(const std::string& word, std::vector& output) const { const std::string& delimeter = resolve("IFS"); std::string trimmed(word); boost::trim_if(trimmed, boost::is_any_of(delimeter)); if(trimmed == "") return; std::vector splitted_values; boost::split(splitted_values, trimmed, boost::is_any_of(delimeter), boost::token_compress_on); output.insert(output.end(), splitted_values.begin(), splitted_values.end()); } void interpreter::define_function_arguments(scope& current_stack, const std::vector& arguments) { std::map positional_args; for(auto i = 1u; i <= arguments.size(); ++i) positional_args[i] = arguments[i - 1]; current_stack["*"].reset(new variable("*", positional_args)); } namespace { bool check_function_name(const std::string& name) { using namespace boost::xpressive; sregex bash_name_pattern = !digit >> ~(set[range('0', '9') | (set= '$', '\'', '"', '(', ')', ' ', '\n', '\r')]) >> *(~(set= '$', '\'', '"', '(', ')', ' ', '\n', '\r')); return regex_match(name, bash_name_pattern); } } void interpreter::define_function(const std::string& name, ANTLR3_MARKER body_index) { if(!check_function_name(name)) throw libbash::parse_exception("illegal function name: " + name); functions.insert(make_pair(name, function(*ast_stack.top(), body_index))); } void interpreter::call(const std::string& name, const std::vector& arguments) { // Prepare arguments define_function_arguments(local_members.back(), arguments); auto iter = functions.find(name); if(iter != functions.end()) iter->second.call(*this); else throw libbash::runtime_exception(name + " is not defined."); } void interpreter::replace_all(std::string& value, const boost::xpressive::sregex& pattern, const std::string& replacement) { value = boost::xpressive::regex_replace(value, pattern, replacement, boost::xpressive::regex_constants::format_literal); } void interpreter::lazy_remove_at_end(std::string& value, const boost::xpressive::sregex& pattern) { boost::xpressive::smatch what; if(boost::xpressive::regex_match(value, what, pattern)) value = what[1]; } void interpreter::replace_first(std::string& value, const boost::xpressive::sregex& pattern, const std::string& replacement) { value = boost::xpressive::regex_replace(value, pattern, replacement, boost::xpressive::regex_constants::format_literal | boost::xpressive::regex_constants::format_first_only); } void interpreter::trim_trailing_eols(std::string& value) { boost::trim_right_if(value, boost::is_any_of("\n")); } void interpreter::get_all_function_names(std::vector& function_names) const { boost::copy(functions | boost::adaptors::map_keys, back_inserter(function_names)); } namespace { void check_unset_positional(const std::string& name) { // Unsetting positional parameters is not allowed if(isdigit(name[0])) throw libbash::runtime_exception("unset: not a valid identifier"); } } void interpreter::unset(const std::string& name) { check_unset_positional(name); auto unsetter = [&](scope& frame) -> bool { auto iter_local = frame.find(name); if(iter_local != frame.end()) { if(iter_local->second->is_readonly()) throw libbash::readonly_exception("unset a readonly variable"); frame.erase(iter_local); return true; } return false; }; if(std::none_of(local_members.rbegin(), local_members.rend(), unsetter)) unsetter(members); } // We need to return false when unsetting readonly functions in future void interpreter::unset_function(const std::string& name) { auto function = functions.find(name); if(function != functions.end()) functions.erase(name); } void interpreter::unset(const std::string& name, const unsigned index) { check_unset_positional(name); auto var = resolve_variable(name); if(var) { if(var->is_readonly()) throw libbash::readonly_exception("unset a readonly variable"); var->unset_value(index); } } bool interpreter::get_additional_option(const std::string& name) const { auto iter = additional_options.find(name); if(iter == additional_options.end()) throw libbash::illegal_argument_exception("Invalid bash option"); return iter->second; } void interpreter::set_additional_option(const std::string& name, bool value) { auto iter = additional_options.find(name); if(iter == additional_options.end()) throw libbash::illegal_argument_exception(name + " is not a valid bash option"); iter->second = value; } long interpreter::eval_arithmetic(const std::string& expression) { bash_ast ast(std::stringstream(expression), &bash_ast::parser_arithmetics); return ast.interpret_with(*this, &bash_ast::walker_arithmetics); } int interpreter::shift(int shift_number) { auto parameters = resolve_variable("*"); if(shift_number < 0) return 1; return parameters->shift(static_cast(shift_number)); }