diff options
-rw-r--r-- | bashast/bashast.g | 12 | ||||
-rw-r--r-- | bashast/gunit/array.gunit | 12 | ||||
-rw-r--r-- | bashast/libbashWalker.g | 44 | ||||
-rw-r--r-- | scripts/function_def.bash | 8 | ||||
-rw-r--r-- | scripts/function_def.bash.result | 8 | ||||
-rw-r--r-- | scripts/var_def.bash | 3 | ||||
-rw-r--r-- | scripts/var_def.bash.result | 2 | ||||
-rw-r--r-- | scripts/var_expansion.bash | 25 | ||||
-rw-r--r-- | scripts/var_expansion.bash.result | 23 | ||||
-rw-r--r-- | src/core/interpreter.cpp | 134 | ||||
-rw-r--r-- | src/core/interpreter.h | 24 | ||||
-rwxr-xr-x | test/script_compiler.sh | 5 |
12 files changed, 179 insertions, 121 deletions
diff --git a/bashast/bashast.g b/bashast/bashast.g index 2115620..fde2c7e 100644 --- a/bashast/bashast.g +++ b/bashast/bashast.g @@ -316,15 +316,17 @@ parameter_replace_operator //either directly or through array var_name : num + | name LSQUARE AT RSQUARE -> ^(ARRAY name AT) + | name LSQUARE TIMES RSQUARE -> ^(ARRAY name TIMES) | var_name_no_digit - | TIMES | DOLLAR - | AT; + | TIMES + | AT + | POUND; //Inside arithmetic we can't allow digits var_name_no_digit - : name^ LSQUARE! (AT|TIMES|explicit_arithmetic) RSQUARE! - | name - | POUND; + : name^ LSQUARE! (explicit_arithmetic) RSQUARE! + | name; //with bang the array syntax is used for array indexes var_name_for_bang : num|name|POUND; diff --git a/bashast/gunit/array.gunit b/bashast/gunit/array.gunit index b9cb121..3a15c20 100644 --- a/bashast/gunit/array.gunit +++ b/bashast/gunit/array.gunit @@ -43,11 +43,11 @@ var_ref: "${asdf[4]//pattern}" -> (VAR_REF (REPLACE_ALL (asdf 4) (STRING pattern))) "${asdf}" -> (VAR_REF asdf) "${#asdf[0]}" -> (VAR_REF (# (asdf 0))) -"${asdf[@]}" -> (VAR_REF (asdf @)) -"${asdf[*]}" -> (VAR_REF (asdf *)) +"${asdf[@]}" -> (VAR_REF (ARRAY asdf @)) +"${asdf[*]}" -> (VAR_REF (ARRAY asdf *)) "${#asdf[@]}" -> (VAR_REF (# (asdf ARRAY_SIZE))) "${#asdf[*]}" -> (VAR_REF (# (asdf ARRAY_SIZE))) -"${asdf[@]:0:1}" -> (VAR_REF (OFFSET (asdf @) 0 1)) -"${asdf[*]#path}" -> (VAR_REF (LAZY_REMOVE_AT_START (asdf *) (STRING path))) -"${asdf[@]%word}" -> (VAR_REF (LAZY_REMOVE_AT_END (asdf @) (STRING word))) -"${asdf[*]/pattern/string}" -> (VAR_REF (REPLACE_FIRST (asdf *) (STRING pattern) (STRING string))) +"${asdf[@]:0:1}" -> (VAR_REF (OFFSET (ARRAY asdf @) 0 1)) +"${asdf[*]#path}" -> (VAR_REF (LAZY_REMOVE_AT_START (ARRAY asdf *) (STRING path))) +"${asdf[@]%word}" -> (VAR_REF (LAZY_REMOVE_AT_END (ARRAY asdf @) (STRING word))) +"${asdf[*]/pattern/string}" -> (VAR_REF (REPLACE_FIRST (ARRAY asdf *) (STRING pattern) (STRING string))) diff --git a/bashast/libbashWalker.g b/bashast/libbashWalker.g index 962a57d..8b0e74c 100644 --- a/bashast/libbashWalker.g +++ b/bashast/libbashWalker.g @@ -367,12 +367,20 @@ var_name returns[std::string libbash_value, unsigned index] @init { $var_name.index = 0; } - :libbash_string=num { $libbash_value = libbash_string; } + :libbash_string=num { + $index = boost::lexical_cast<unsigned>(libbash_string); + $libbash_value = ($index != 0 ? "*" : "0"); + } |name { $libbash_value = $name.libbash_value; $index = $name.index; - } - |TIMES { $libbash_value = "*"; }; + }; + +array_name returns[std::string libbash_value] + :^(ARRAY name (AT|TIMES)) { $libbash_value = $name.libbash_value; } + // We do not care the difference between TIMES and AT for now + |TIMES { $libbash_value = "*"; } + |AT { $libbash_value = "*"; }; var_expansion returns[std::string libbash_value] @declarations { @@ -389,6 +397,12 @@ var_expansion returns[std::string libbash_value] |^(USE_ALTERNATE_WHEN_UNSET_OR_NULL var_name libbash_word=word) { libbash_value = walker->do_alternate_expansion($var_name.libbash_value, libbash_word, $var_name.index); } + |(^(OFFSET array_name arithmetics arithmetics)) => ^(OFFSET libbash_name=array_name offset=arithmetics length=arithmetics) { + libbash_value = walker->do_subarray_expansion(libbash_name, offset, length); + } + |(^(OFFSET array_name offset=arithmetics)) => ^(OFFSET libbash_name=array_name offset=arithmetics) { + libbash_value = walker->do_subarray_expansion(libbash_name, offset); + } |(^(OFFSET var_name arithmetics arithmetics)) => ^(OFFSET var_name offset=arithmetics length=arithmetics) { libbash_value = walker->do_substring_expansion($var_name.libbash_value, offset, length, $var_name.index); } @@ -455,27 +469,11 @@ word returns[std::string libbash_value] //variable reference var_ref [bool double_quoted] returns[std::string libbash_value] - :^(VAR_REF name) { - $libbash_value = walker->resolve<std::string>($name.libbash_value, $name.index); - } - |^(VAR_REF libbash_string=num) { - $libbash_value = walker->resolve<std::string>(libbash_string); - } - |^(VAR_REF ^(libbash_string=name_base AT)) { walker->get_all_elements(libbash_string, $libbash_value); } - |^(VAR_REF ^(libbash_string=name_base TIMES)) { - if(double_quoted) - walker->get_all_elements_IFS_joined(libbash_string, $libbash_value); - else - walker->get_all_elements(libbash_string, $libbash_value); - } - |^(VAR_REF TIMES) { - if(double_quoted) - walker->get_all_elements_IFS_joined("*", $libbash_value); - else - walker->get_all_elements("*", $libbash_value); + :^(VAR_REF var_name) { + $libbash_value = walker->resolve<std::string>($var_name.libbash_value, $var_name.index); } - |^(VAR_REF AT) { std::cerr << "$@ has not been implemented yet" << std::endl; } - |^(VAR_REF POUND) { std::cerr << "$# has not been implemented yet" << std::endl; } + |^(VAR_REF libbash_string=array_name) { walker->get_all_elements_IFS_joined(libbash_string, $libbash_value); } + |^(VAR_REF POUND) { $libbash_value = boost::lexical_cast<std::string>(walker->get_array_length("*")); } |^(VAR_REF QMARK) { $libbash_value = walker->get_status<std::string>(); } |^(VAR_REF MINUS) { std::cerr << "$- has not been implemented yet" << std::endl; } |^(VAR_REF BANG) { std::cerr << "$! has not been implemented yet" << std::endl; } diff --git a/scripts/function_def.bash b/scripts/function_def.bash index 53e5ec8..8b7bd50 100644 --- a/scripts/function_def.bash +++ b/scripts/function_def.bash @@ -50,6 +50,14 @@ func_nested2() { local foo_nested=hi bar_nested=(1 2 3) localbar localbar=1 + echo ${bar_nested[@]} + echo ${not_exist[@]} + echo ${#bar_nested[@]} + echo ${#non_exist[@]} + echo ${#foo_nested} + echo ${#non_exist} + echo ${foo_nested:-wrong} + echo ${non_exist:-right} func_nested1 } func_nested2 diff --git a/scripts/function_def.bash.result b/scripts/function_def.bash.result index cebbfae..a4af30a 100644 --- a/scripts/function_def.bash.result +++ b/scripts/function_def.bash.result @@ -1,3 +1,11 @@ +1 2 3 + +3 +0 +2 +0 +hi +right hi 1 1 overloaded let diff --git a/scripts/var_def.bash b/scripts/var_def.bash index f832a96..0989f9a 100644 --- a/scripts/var_def.bash +++ b/scripts/var_def.bash @@ -35,9 +35,6 @@ ARRAY10="${ARRAY05[*]}" FOO001="networkmanager" FOO002="0.8.2" FOO003=${FOO001}-${FOO002} -FOO004=$* -FOO004=$@ -FOO004=$# FOO004=$? FOO004=$- FOO004=$! diff --git a/scripts/var_def.bash.result b/scripts/var_def.bash.result index 121c267..b9b415e 100644 --- a/scripts/var_def.bash.result +++ b/scripts/var_def.bash.result @@ -1,5 +1,3 @@ -$@ has not been implemented yet -$# has not been implemented yet $- has not been implemented yet $! has not been implemented yet ARRAY01=1 2 3 4 5 diff --git a/scripts/var_expansion.bash b/scripts/var_expansion.bash index d38d8aa..9f42f19 100644 --- a/scripts/var_expansion.bash +++ b/scripts/var_expansion.bash @@ -84,12 +84,21 @@ FOO081=${FOO039//@([a-c]|[k-m])} target="abc123abc" FOO082="${target##+(ab[c])*([[:digit:]])}" function positional_parameter_test(){ - FOO083=${*} - FOO084=${*:1} - FOO085=${*:1:2} - FOO086=${*: -1} - FOO087=${*: -2:5} - FOO088=${*:0} + FOO083=$* + FOO084=${*} + FOO085=${*:1} + FOO086=${*:1:2} + FOO087=${*: -1} + FOO088=${*: -2:5} + FOO089=${*:0} + FOO090=$@ + FOO091=${@} + FOO092=${@:1} + FOO093=${@:1:2} + FOO094=${@: -1} + FOO095=${@: -2:5} + FOO096=${@:0} + echo $# } positional_parameter_test 1 2 3 4 5 target="abc*abc" @@ -97,3 +106,7 @@ echo ${target/*} echo ${target/'*'} echo ${target/"*"} : ${FOO089:=} +ARRAY=(1 2 3 4 5) +echo ${ARRAY[@]:1} +echo ${ARRAY[@]:1:3} +echo $# diff --git a/scripts/var_expansion.bash.result b/scripts/var_expansion.bash.result index c3f30c8..bd83c62 100644 --- a/scripts/var_expansion.bash.result +++ b/scripts/var_expansion.bash.result @@ -1,7 +1,11 @@ +5 abcabc abcabc -ARRAY=hi hello 1 2 3 +2 3 4 5 +2 3 4 +0 +ARRAY=1 2 3 4 5 ARRAY2=hello EAPI=3 EAPI4=4 @@ -88,9 +92,16 @@ FOO081=Heo Word FOO082=abc FOO083=1 2 3 4 5 FOO084=1 2 3 4 5 -FOO085=1 2 -FOO086=5 -FOO087=4 5 -FOO088=filename 1 2 3 4 5 -FOO089= +FOO085=1 2 3 4 5 +FOO086=1 2 +FOO087=5 +FOO088=4 5 +FOO089=@srcdir@/scripts/var_expansion.bash 1 2 3 4 5 +FOO090=1 2 3 4 5 +FOO091=1 2 3 4 5 +FOO092=1 2 3 4 5 +FOO093=1 2 +FOO094=5 +FOO095=4 5 +FOO096=@srcdir@/scripts/var_expansion.bash 1 2 3 4 5 target=abc*abc diff --git a/src/core/interpreter.cpp b/src/core/interpreter.cpp index fedbee4..78ffd89 100644 --- a/src/core/interpreter.cpp +++ b/src/core/interpreter.cpp @@ -90,22 +90,12 @@ std::shared_ptr<variable> interpreter::resolve_variable(const std::string& name) if(name.empty()) return std::shared_ptr<variable>(); - // positional parameter - if(isdigit(name[0]) && !local_members.empty()) + BOOST_REVERSE_FOREACH(auto& frame, local_members) { - auto iter_local = local_members.back().find(name); - if(iter_local != local_members.back().end()) + auto iter_local = frame.find(name); + if(iter_local != frame.end()) return iter_local->second; } - else - { - 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()) @@ -116,53 +106,22 @@ std::shared_ptr<variable> interpreter::resolve_variable(const std::string& name) bool interpreter::is_unset_or_null(const std::string& name, const unsigned index) const { - auto i = members.find(name); - if(i == members.end()) + auto var = resolve_variable(name); + if(!var) return true; - else - return i->second->is_null(index); + return var->is_null(index); } -// This method temporarily supports array offset expansion for $* and $@. -// That logic will be refactored and applied to normal array variables in future. std::string interpreter::get_substring(const std::string& name, long long offset, unsigned length, const unsigned index) const { - if(name != "*" && name != "@") - { - std::string value = resolve<std::string>(name, index); - if(!get_real_offset(offset, boost::numeric_cast<unsigned>(value.size()))) - return ""; - // After get_real_offset, we know offset can be cast to unsigned. - return value.substr(boost::numeric_cast<std::string::size_type>(offset), length); - } - else - { - std::vector<std::string> array; - // ${*:1} has the same content as ${*}, ${*:0} contains current script name as the first element - if(offset > 0) - offset--; - else if(offset == 0) - // Need to replace this with the real script name - array.push_back("filename"); - // We do not support arrays that have size bigger than numeric_limits<unsigned>::max() - if(resolve_array(name, array) && get_real_offset(offset, boost::numeric_cast<unsigned>(array.size()))) - { - // We do not support arrays that have size bigger than numeric_limits<unsigned>::max() - // After get_real_offset, we know offset can be cast to unsigned. - unsigned max_length = boost::numeric_cast<unsigned>(array.size()) - boost::numeric_cast<unsigned>(offset); - if(length > max_length) - length = max_length; - - auto start = array.begin() + boost::numeric_cast<std::vector<std::string>::difference_type>(offset); - auto end = array.begin() + boost::numeric_cast<std::vector<std::string>::difference_type>(offset + length); - return boost::algorithm::join(std::vector<std::string>(start, end), resolve<std::string>("IFS").substr(0, 1)); - } - else - return ""; - } + std::string value = resolve<std::string>(name, index); + if(!get_real_offset(offset, boost::numeric_cast<unsigned>(value.size()))) + return ""; + // After get_real_offset, we know offset can be cast to unsigned. + return value.substr(boost::numeric_cast<std::string::size_type>(offset), length); } const std::string interpreter::do_substring_expansion(const std::string& name, @@ -183,6 +142,54 @@ const std::string interpreter::do_substring_expansion(const std::string& name, return get_substring(name, offset, boost::numeric_cast<unsigned>(length), index); } +std::string interpreter::get_subarray(const std::string& name, + long long offset, + unsigned length) const +{ + std::vector<std::string> 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<std::string>("0")); + } + // We do not support arrays that have size bigger than numeric_limits<unsigned>::max() + if(resolve_array(name, array) && get_real_offset(offset, boost::numeric_cast<unsigned>(array.size()))) + { + // We do not support arrays that have size bigger than numeric_limits<unsigned>::max() + // After get_real_offset, we know offset can be cast to unsigned. + unsigned max_length = boost::numeric_cast<unsigned>(array.size()) - boost::numeric_cast<unsigned>(offset); + if(length > max_length) + length = max_length; + + auto start = array.begin() + boost::numeric_cast<std::vector<std::string>::difference_type>(offset); + auto end = array.begin() + boost::numeric_cast<std::vector<std::string>::difference_type>(offset + length); + return boost::algorithm::join(std::vector<std::string>(start, end), resolve<std::string>("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<unsigned>::max()); +} + +const std::string interpreter::do_subarray_expansion(const std::string& name, + long long offset, + int length) const +{ + if(length < 0) + throw interpreter_exception("length of substring expression should be greater or equal to zero"); + + return get_subarray(name, offset, boost::numeric_cast<unsigned>(length)); +} + std::string interpreter::do_replace_expansion(const std::string& name, std::function<void(std::string&)> replacer, const unsigned index) const @@ -195,19 +202,18 @@ std::string interpreter::do_replace_expansion(const std::string& name, std::string::size_type interpreter::get_length(const std::string& name, const unsigned index) const { - auto i = members.find(name); - if(i == members.end()) + auto var = resolve_variable(name); + if(!var) return 0; - return i->second->get_length(index); + return var->get_length(index); } variable::size_type interpreter::get_array_length(const std::string& name) const { - auto i = members.find(name); - if(i == members.end()) + auto var = resolve_variable(name); + if(!var) return 0; - else - return i->second->get_array_length(); + return var->get_array_length(); } void interpreter::get_all_elements_joined(const std::string& name, @@ -255,14 +261,10 @@ void interpreter::define_function_arguments(scope& current_stack, { std::map<unsigned, std::string> positional_args; - for(auto i = 0u; i != arguments.size(); ++i) - { - const std::string& name = boost::lexical_cast<std::string>(i + 1); - current_stack[name].reset(new variable(name, arguments[i])); - positional_args[i] = arguments[i]; - } + for(auto i = 1u; i <= arguments.size(); ++i) + positional_args[i] = arguments[i - 1]; - define("*", positional_args); + current_stack["*"].reset(new variable("*", positional_args, true)); } void interpreter::call(const std::string& name, diff --git a/src/core/interpreter.h b/src/core/interpreter.h index 937edd8..460a2e3 100644 --- a/src/core/interpreter.h +++ b/src/core/interpreter.h @@ -93,6 +93,10 @@ class interpreter: public boost::noncopyable long long offset, unsigned length, const unsigned index) const; + + std::string get_subarray(const std::string& name, + long long offset, + unsigned length) const; public: typedef std::map<std::string, bool>::const_iterator option_iterator; @@ -194,11 +198,11 @@ public: template <typename T> bool resolve_array(const std::string& name, std::vector<T>& values) const { - auto i = members.find(name); - if(i == members.end()) + auto var = resolve_variable(name); + if(!var) return false; - i->second->get_all_values(values); + var->get_all_values(values); return true; } @@ -398,6 +402,20 @@ public: int length, const unsigned index) const; + /// \brief perform subarray expansion + /// \param 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 the offset of the subarray + /// \param 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 the name of the varaible that needs to be expanded /// \param the function object used to perform expansion diff --git a/test/script_compiler.sh b/test/script_compiler.sh index 021855e..6ff5672 100755 --- a/test/script_compiler.sh +++ b/test/script_compiler.sh @@ -1,11 +1,14 @@ #!/bin/sh declare -i error=0 +result=$(mktemp) for script in $@ do - ./variable_printer $script 2>&1 | diff -u $script.result - + sed "s/@srcdir@/$srcdir/" $script.result > $result + ./variable_printer $script 2>&1 | diff -u $result - error+=$? done +rm -rf $result exit $error |