/*
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.h
/// \brief implementation for bash interpreter (visitor pattern)
///
#ifndef LIBBASH_CORE_INTERPRETER_H_
#define LIBBASH_CORE_INTERPRETER_H_
#include
#include
#include
#include
#include
#include
#include "core/function.h"
#include "core/symbols.hpp"
#include "cppbash_builtin.h"
/// \brief symbol table implementation
typedef std::unordered_map> scope;
///
/// \class interpreter
/// \brief implementation for bash interpreter
///
class interpreter: public boost::noncopyable
{
/// \brief global symbol table for variables
scope members;
/// \brief global symbol table for functions
std::unordered_map functions;
std::stack ast_stack;
/// \brief local scope for function arguments, execution environment and
/// local variables
std::vector local_members;
std::ostream* _out;
std::ostream* _err;
std::istream* _in;
// std::map is chosen for sorted output in shopt -p
std::map additional_options;
// std::map is chosen for sorted output in $-. The order may not be the same
// as bash implementation.
std::map options;
/// \brief the return status of the last command
int status;
/// \brief calculate the correct offset when offset < 0 and check whether
/// the real offset is in legal range
/// \param[in,out] offset a value/result argument referring to offset
/// \param[in] size the size of the original string
/// \return whether the real offset is in legal range
bool get_real_offset(long long& offset, const unsigned size) const
{
offset = (offset >= 0? offset : size + offset);
return !(offset < 0 || offset >= size);
}
void get_all_elements_joined(const std::string& name,
const std::string& delim,
std::string& result) const;
std::shared_ptr resolve_variable(const std::string&) const;
void define_function_arguments(scope& current_stack,
const std::vector& arguments);
std::string get_substring(const std::string& name,
long long offset,
unsigned length,
const unsigned index) const;
std::string get_subarray(const std::string& name,
long long offset,
unsigned length) const;
public:
/// bash option iterator
typedef std::map::const_iterator option_iterator;
///
/// \class local_scope
/// \brief RAII concept for local scope management
///
class local_scope
{
interpreter& walker;
public:
/// \brief construtor
/// \param w the reference to the interpreter object
local_scope(interpreter& w): walker(w)
{
walker.local_members.push_back(scope());
}
/// \brief the destructor
~local_scope()
{
walker.local_members.pop_back();
}
};
/// \brief construtor
interpreter();
///
/// \brief return the number of variables
/// \return the number of variables
scope::size_type size() const
{
return members.size();
}
///
/// \brief return an iterator referring to the first variable
/// \return iterator referring to the first variable
scope::iterator begin()
{
return members.begin();
}
///
/// \brief return a const iterator referring to the first variable
/// \return const iterator referring to the first variable
scope::const_iterator begin() const
{
return members.begin();
}
///
/// \brief return an iterator referring to the next element after the
/// last variable
/// \return iterator referring to he next element after the last variable
scope::iterator end()
{
return members.end();
}
///
/// \brief return a const iterator referring to the next element after
/// the last variable
/// \return const iterator referring to he next element after the last
/// variable
scope::const_iterator end() const
{
return members.end();
}
///
/// \brief checks whether the current scope is local or global
/// \return whether current scope is local
bool is_local_scope() const
{
return local_members.size() > 0;
}
/// \brief set current output stream
/// \param stream the pointer to the output stream
void set_output_stream(std::ostream* stream)
{
_out = stream;
}
/// \brief get current output stream
/// \return the pointer to the output stream
std::ostream* get_output_stream()
{
return _out;
}
/// \brief restore the current output stream to standard output stream
void restore_output_stream()
{
_out = &std::cout;
}
/// \brief set current error stream
/// \param stream the pointer to the error stream
void set_error_stream(std::ostream* stream)
{
_err = stream;
}
/// \brief get current error stream
/// \return the pointer to the error stream
std::ostream* get_error_stream()
{
return _err;
}
/// \brief set current input stream
/// \param stream the pointer to the input stream
void set_input_stream(std::istream* stream)
{
_in = stream;
}
/// \brief get current input stream
/// \return the pointer to the input stream
std::istream* get_input_stream()
{
return _in;
}
/// \brief check whether a variable is valid and can be used
/// \param var variable
/// \return whether the variable is valid
bool is_valid(const std::shared_ptr& var, const std::string& name) const
{
if(var)
return true;
if(get_option('u'))
throw libbash::unsupported_exception(name + ": unbound variable");
return false;
}
/// \brief resolve string/long variable, local scope will be
/// checked first, then global scope
/// \param name variable name
/// \param index array index, use index=0 if it's not an array
/// \return the value of the variable, call default constructor if
/// it's undefined
template
T resolve(const std::string& name, const unsigned index=0) const
{
auto var = resolve_variable(name);
if(is_valid(var, name))
{
if(get_option('u') && var->is_unset(index))
{
if(name == "*")
throw libbash::unsupported_exception("$" + boost::lexical_cast(index) + ": unbound variable");
else if(index == 0)
throw libbash::unsupported_exception(name + ": unbound variable");
else
throw libbash::unsupported_exception(name + "[" + boost::lexical_cast(index) + "]: unbound variable");
}
return var->get_value(index);
}
else
{
if(get_option('u'))
throw libbash::unsupported_exception(name + ": unbound variable");
return T{};
}
}
/// \brief resolve array variable
/// \param name variable name
/// \param[out] values vector that stores all array values
template
bool resolve_array(const std::string& name, std::vector& values) const
{
auto var = resolve_variable(name);
if(!is_valid(var, name))
return false;
var->get_all_values(values);
return true;
}
/// \brief check whether the value of the variable is null, return true
/// if the variable is undefined
/// \param name variable name
/// \param index array index, use index=0 if it's not an array
/// \return whether the value of the variable is null
bool is_unset_or_null(const std::string& name, const unsigned index) const;
/// \brief check whether the value of the variable is unset
/// \param name variable name
/// \return whether the value of the variable is unset
bool is_unset(const std::string& name) const
{
return members.find(name) == members.end();
}
/// \brief update the variable value, raise libbash::interpreter_exception if
/// it's readonly, will define the variable if it doesn't exist
/// \param name variable name
/// \param new_value new value
/// \param index array index, use index=0 if it's not an array
/// \return the new value of the variable
template
const T& set_value(const std::string& name,
const T& new_value,
const unsigned index=0)
{
auto var = resolve_variable(name);
if(var)
var->set_value(new_value, index);
else
define(name, new_value, false, index);
return new_value;
}
/// \brief set the return status of the last command
/// \param s the value of the return status
void set_status(int s)
{
status = s;
}
/// \brief get the return status of the last command
int get_status(void) const
{
return status;
}
/// \brief unset a variable
/// \param name the name of the variable
void unset(const std::string& name);
/// \brief unset a function
/// \param name the name of the function
void unset_function(const std::string& name);
/// \brief unset a array member
/// \param name the name of the array
/// \param index the index of the member
void unset(const std::string& name, const unsigned index);
/// \brief define a new global variable
/// \param name the name of the variable
/// \param value the value of the variable
/// \param readonly whether it's readonly, default is false
/// \param index whether it's null, default is false
template
void define(const std::string& name,
const T& value,
bool readonly=false,
const unsigned index=0)
{
members[name].reset(new variable(name, value, readonly, index));
}
/// \brief define a new local variable
/// \param name the name of the variable
/// \param value the value of the variable
/// \param readonly whether it's readonly, default is false
/// \param index whether it's null, default is false
template
void define_local(const std::string& name,
const T& value,
bool readonly=false,
const unsigned index=0)
{
if(local_members.empty())
throw libbash::runtime_exception("Define local variables outside function scope");
local_members.back()[name].reset(new variable(name, value, readonly, index));
}
/// \brief define a new function
/// \param name the name of the function
/// \param body_index the body index of the function
void define_function(const std::string& name,
ANTLR3_MARKER body_index);
/// \brief push current AST, used for function definition
/// \param ast the pointer to the current ast
void push_current_ast(bash_ast* ast)
{
ast_stack.push(ast);
}
/// \brief pop current AST, used for function definition
void pop_current_ast()
{
ast_stack.pop();
}
/// \brief make function call
/// \param name function name
/// \param arguments function arguments
void call(const std::string& name,
const std::vector& arguments);
/// \brief check if we have 'name' defined as a function
/// \param name function name
/// \return whether 'name' is a function
bool has_function(const std::string& name) const
{
return functions.find(name) != functions.end();
}
/// \brief get all defined function names
/// \param[out] function_names the place to store the function names
void get_all_function_names(std::vector& function_names) const;
/// \brief execute builtin
/// \param name builtin name
/// \param args builtin arguments
/// \param output the output stream
/// \param error the error stream
/// \param input the input stream
/// \return the return value of the builtin
int execute_builtin(const std::string& name,
const std::vector& args,
std::ostream* output=0,
std::ostream* error=0,
std::istream* input=0)
{
return cppbash_builtin::exec(name,
args,
output == 0 ? *_out : *output,
error == 0 ? *_err : *error,
input == 0 ? *_in : *input,
*this);
}
/// \brief perform ${parameter:−word} expansion
/// \param cond whether to perform expansion
/// \param name the name of the parameter
/// \param value the value of the word
/// \param index the index of the paramter
/// \return the expansion result
const std::string do_default_expansion(bool cond,
const std::string& name,
const std::string& value,
const unsigned index) const
{
return (cond ? value : resolve(name, index));
}
/// \brief perform ${parameter:=word} expansion
/// \param cond whether to perform expansion
/// \param name the name of the parameter
/// \param value the value of the word
/// \param index the index of the paramter
/// \return the expansion result
const std::string do_assign_expansion(bool cond,
const std::string& name,
const std::string& value,
const unsigned index)
{
return (cond ? set_value(name, value, index) : resolve(name, index));
}
/// \brief perform ${parameter:+word} expansion
/// \param cond whether to perform expansion
/// \param value the value of the word
/// \return the expansion result
const std::string do_alternate_expansion(bool cond,
const std::string& value) const
{
return (cond ? "" : value);
}
/// \brief perform substring expansion
/// \param name the name of the parameter
/// \param offset the offset of the substring
/// \param index the index of the paramter
/// \return the expansion result
const std::string do_substring_expansion(const std::string& name,
long long offset,
const unsigned index) const;
/// \brief perform substring expansion
/// \param name the name of the parameter
/// \param offset the offset of the substring
/// \param length the length of the substring
/// \param index the index of the paramter
/// \return the expansion result
const std::string do_substring_expansion(const std::string& name,
long long offset,
int length,
const unsigned index) const;
/// \brief perform subarray expansion
/// \param name the name of the parameter
/// \param offset the offset of the subarray
/// \return the expansion result
const std::string do_subarray_expansion(const std::string& name,
long long offset) const;
/// \brief perform subarray expansion
/// \param name the name of the parameter
/// \param offset the offset of the subarray
/// \param length the length of the subarray
/// \return the expansion result
const std::string do_subarray_expansion(const std::string& name,
long long offset,
int length) const;
/// \brief perform replacement expansion
/// \param name the name of the variable that needs to be expanded
/// \param replacer the function object used to perform expansion
/// \param index array index, use index=0 if it's not an array
/// \return the expanded value
std::string do_replace_expansion(const std::string& name,
std::function replacer,
const unsigned index) const;
/// \brief perform array replacement expansion
/// \param name the name of the array that needs to be expanded
/// \param replacer the function object used to perform expansion
/// \return the expanded value
std::string do_array_replace_expansion(const std::string& name,
std::function replacer) const;
/// \brief get the length of a string variable
/// \param name the name of the variable
/// \param index the index of the variable
/// \return the length
std::string::size_type get_length(const std::string& name, const unsigned index=0) const;
/// \brief get the length of an array
/// \param name the name of the array
/// \return the length of the array
variable::size_type get_array_length(const std::string& name) const;
/// \brief get the max index of an array
/// \param name the name of the array
/// \return the max index of the array
variable::size_type get_max_index(const std::string& name) const
{
auto var = resolve_variable(name);
if(is_valid(var, name))
return var->get_max_index();
else
return 0;
}
/// \brief get all array elements concatenated by space
/// \param name the name of the array
/// \param[out] result the concatenated string
void get_all_elements(const std::string& name, std::string& result) const;
/// \brief get all array elements concatenated by the first character of IFS
/// \param name the name of the array
/// \param[out] result the concatenated string
void get_all_elements_IFS_joined(const std::string& name, std::string& result) const;
/// \brief implementation of word splitting
/// \param word the value of the word
///.\param[out] output the splitted result will be appended to output
void split_word(const std::string& word, std::vector& output) const;
/// \brief get the status of shell optional behavior
/// \param name the option name
/// \return zero unless the name is not a valid shell option
bool get_additional_option(const std::string& name) const;
/// \brief set the status of shell optional behavior
/// \param name the option name
/// \param[in] value true if option is enabled, false otherwise
/// \return zero unless the name is not a valid shell option
void set_additional_option(const std::string& name, bool value);
/// \brief get the status of shell optional behavior
/// \param name the option name
/// \return zero unless the name is not a valid shell option
bool get_option(const char name) const;
/// \brief set the status of shell optional behavior
/// \param name the option name
/// \param[in] value true if option is enabled, false otherwise
/// \return zero unless the name is not a valid shell option
void set_option(const char name, bool value);
/// \brief return an iterator referring to the first variable
/// \return iterator referring to the first variable
option_iterator additional_options_begin() const
{
return additional_options.begin();
}
/// \brief return an iterator referring to the next element after the
/// last variable
/// \return iterator referring to he next element after the last variable
option_iterator additional_options_end() const
{
return additional_options.end();
}
/// \brief evaluate arithmetic expression and return the result
/// \param expression the arithmetic expression
/// \return the evaluated result
long eval_arithmetic(const std::string& expression);
/// \brief shift the positional parameters to the left by n.
/// \param shift_number the number to be shifted
/// \return zero unless n is greater than $# or less than zero, non-zero otherwise.
int shift(int shift_number);
void define_positional_arguments(const std::vector::const_iterator begin,
const std::vector::const_iterator end);
/// \brief perform expansion like ${var//foo/bar}
/// \param value the value to be expanded
/// \param pattern the pattern used to match the value
/// \param replacement the replacement string
static void replace_all(std::string& value,
const boost::xpressive::sregex& pattern,
const std::string& replacement);
/// \brief perform expansion like ${var%foo}
/// \param value the value to be expanded
/// \param pattern the pattern used to match the value
static void lazy_remove_at_end(std::string& value,
const boost::xpressive::sregex& pattern);
/// \brief perform expansion like ${var/foo/bar}
/// \param value the value to be expanded
/// \param pattern the pattern used to match the value
/// \param replacement the replacement string
static void replace_first(std::string& value,
const boost::xpressive::sregex& pattern,
const std::string& replacement);
/// \brief remove trailing EOLs from the value
/// \param[in, out] value the target
static void trim_trailing_eols(std::string& value);
};
#endif