# -*- shell-script -*- # fns.sh - Debugger Utility Functions # # Copyright (C) 2002, 2003, 2004, 2005, 2007, 2008, 2009, 2010, 2011 # Rocky Bernstein # # This program 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, or # (at your option) any later version. # # This program 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 this program; see the file COPYING. If not, write to # the Free Software Foundation, 59 Temple Place, Suite 330, Boston, # MA 02111 USA. typeset -a _Dbg_yn = ''; setglobal _Dbg_yn = '('"n" "y") # Return $2 copies of $1. If successful, $? is 0 and the return value # is in result. Otherwise $? is 1 and result '' proc _Dbg_copies { setglobal result = '''' sh-expr ' $# < 2 ' && return 1 typeset -r string = $1 typeset -i count = $2 || return 2 sh-expr ' count > 0 ' || return 3 builtin printf -v result "%$(count)s" ' ' || return 3 setglobal result = $(result// /$string) return 0 } # _Dbg_defined returns 0 if $1 is a defined variable or nonzero otherwise. proc _Dbg_defined { sh-expr ' 0 == $# ' && return 1 typeset -p $1 &> /dev/null } # Add escapes to a string $1 so that when it is read back via "$1" # it is the same as $1. proc _Dbg_esc_dq { builtin printf "%q\n" $1 } # We go through the array indirection below because bash (but not ksh) # gives syntax errors when this is put in place. typeset -a _Dbg_eval_re = ''; setglobal _Dbg_eval_re = '( ''^[ \t]*(if|elif)[ \t]+([^;]*)((;[ \t]*then?)?|$)' '^[ \t]*return[ \t]+(.*)$' '^[ \t]*while[ \t]+([^;]*)((;[ \t]*do?)?|$)' '^[ \t]*[A-Za-z_][A-Za-z_0-9_]*[+-]?=(.*$)' "^[ \t]*[A-Za-z_][A-Za-z_0-9_]*\[[0-9]+\][+-]?=(.*\$)" ) # Removes "[el]if" .. "; then" or "while" .. "; do" or "return .." # leaving resumably the expression part. This function is called by # the eval? command where we want to evaluate the expression part in # a source line of code proc _Dbg_eval_extract_condition { setglobal orig = $1 if [[ $orig =~ ${_Dbg_eval_re[0]} ]] { setglobal extracted = $(BASH_REMATCH[2]) } elif [[ $orig =~ ${_Dbg_eval_re[1]} ]] { setglobal extracted = ""echo $(BASH_REMATCH[1])"" } elif [[ $orig =~ ${_Dbg_eval_re[2]} ]] { setglobal extracted = $(BASH_REMATCH[1]) } elif [[ $orig =~ ${_Dbg_eval_re[3]} ]] { setglobal extracted = ""echo $(BASH_REMATCH[1])"" } elif [[ $orig =~ ${_Dbg_eval_re[4]} ]] { setglobal extracted = ""echo $(BASH_REMATCH[1])"" } else { setglobal extracted = $orig } } # Print "on" or "off" depending on whether $1 is true (0) or false # (nonzero). proc _Dbg_onoff { typeset onoff = ''off.'' sh-expr ' $1 != 0 ' && setglobal onoff = ''on.'' builtin echo $onoff } # Set $? to $1 if supplied or the saved entry value of $?. proc _Dbg_set_dol_q { return ${1:-$_Dbg_debugged_exit_code} } # Split $2 using $1 as the split character. We accomplish this by # temporarily resetting the variable IFS (input field separator). # # Example: # typeset -a a=($(_Dbg_split ':' "file:line")) # a[0] will have file and a{1] will have line. proc _Dbg_split { typeset old_IFS = $IFS typeset new_ifs = $(1:-' ') shift typeset -r text = "$ifsjoin(Argv)" typeset -a array = '' setglobal IFS = $new_ifs setglobal array = '( '$text ) echo $(array[@]) setglobal IFS = $old_IFS } # _get_function echoes a list of all of the functions. # if $1 is nonzero, system functions, i.e. those whose name starts with # an underscore (_), are included in the search. # FIXME add parameter search pattern. proc _Dbg_get_functions { typeset -i include_system = $(1:-0) typeset pat = $(2:-.*) typeset -a fns_a = '' setglobal fns_a = '( '$(declare -F) ) typeset -a ret_fns = ''() typeset -i i = '' typeset -i invert = '0'; if [[ $pat == !* ]] { # Remove leading ! setglobal pat = "#{$pat#!}" setglobal invert = '1' } # Iterate skipping over consecutive single tokens "declare" and "-F" for (( i=2; (( i < ${#fns_a[@]} )) ; i += 3 )) ; do typeset fn="${fns_a[$i]}" [[ $fn == _* ]] && (( ! include_system )) && continue if [[ $fn =~ $pat ]] ; then [[ $invert == 0 ]] && ret_fns[${#ret_fns[@]}]=$fn else [[ $invert != 0 ]] && ret_fns[${#ret_fns[@]}]=$fn fi done echo $(ret_fns[@]) } # _Dbg_is_function returns 0 if $1 is a defined function or nonzero otherwise. # if $2 is nonzero, system functions, i.e. those whose name starts with # an underscore (_), are included in the search. proc _Dbg_is_function { sh-expr ' 0 == $# ' && return 1 typeset needed_fn = $1 typeset -i include_system = $(2:-0) [[ ${needed_fn:0:1} == '_' ]] && sh-expr '!include_system' && do { return 1 } declare -F $needed_fn >/dev/null !2 > !1 return $? } # Return 0 if set -x tracing is on proc _Dbg_is_traced { # Is "x" in set options? if [[ $- == *x* ]] { return 0 } else { return 1 } } # Common routine for setup of commands that take a single # linespec argument. We assume the following variables # which we store into: # filename, line_number, full_filename proc _Dbg_linespec_setup { typeset linespec = $(1:-'') if [[ -z $linespec ]] { _Dbg_errmsg "Invalid line specification, null given" } typeset -a word = '' eval "word=($[_Dbg_parse_linespec $linespec])" if [[ ${#word[@]} == 0 ]] { _Dbg_errmsg "Invalid line specification: $linespec" return } setglobal filename = $(word[2]) typeset -ri is_function = $(word[1]) setglobal line_number = $(word[0]) setglobal full_filename = $[_Dbg_is_file $filename] if sh-expr ' is_function ' { if [[ -z $full_filename ]] { _Dbg_readin $filename setglobal full_filename = $[_Dbg_is_file $filename] } } } # Parse linespec in $1 which should be one of # int # file:line # function-num # Return triple (line, is-function?, filename,) # We return the filename last since that can have embedded blanks. proc _Dbg_parse_linespec { typeset linespec = $1 eval $_seteglob match $linespec { # line number only - use _Dbg_frame_last_filename for filename with $int_pat echo "$linespec 0 \"$_Dbg_frame_last_filename\"" # file:line with [^:][^:]*[:]$int_pat # Split the POSIX way typeset line_word = $(linespec##*:) typeset file_word = $(linespec%${line_word}) setglobal file_word = $(file_word%?) echo "$line_word 0 \"$file_word\"" # Function name or error with * if _Dbg_is_function $linespec $_Dbg_set_debug { var -a word = '( '$(declare -F $linespec) ) if [[ 0 == $? && ${#word[@]} > 2 ]] { builtin echo "$(word[1]) 1 $(word[2])" } else { builtin echo '' } } else { builtin echo '' } } } # usage _Dbg_set_ftrace [-u] funcname [funcname...] # Sets or unsets a function for stopping by setting # the -t or +t property to the function declaration. # proc _Dbg_set_ftrace { typeset opt = '-t', tmsg = '"enabled'," func = '' if [[ $1 == -u ]] { setglobal opt = '+t' setglobal tmsg = '"disabled'" shift }for func in @Argv { declare -f $opt $func # _Dbg_msg "Tracing $tmsg for function $func" } }