# -*- shell-script -*-
# gdb-like "backtrace" debugger command
#
#   Copyright (C) 2002-2006, 2008, 2010-2011, 2018-2019
#   2024 Rocky Bernstein <rocky@gnu.org>
#
#   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.

# Print a stack backtrace.
# $1 is an additional offset correction - this routine is called from two
# different places and one routine has one more additional call on top.
# $2 is the maximum number of entries to include.
# $3 is which entry we start from; the "up", "down" and the "frame"
# commands may shift this.

# This code assumes the version of bash where FUNCNAME is an array,
# not a variable.

_Dbg_help_add backtrace \
'**backtrace** [*opts*] [*count*]

Print backtrace of all stack frames, or inner-most *count* frames.

With a negative argument, print outer-most -*count* frames.

An arrow indicates the "current frame". The current frame determines
the context used for many debugger commands such as expression
evaluation or source-line listing.

*opts* are:

   -s | --source  - show source code line
   -h | --help    - give this help

Examples:
---------

   backtrace      # Print a full stack trace
   backtrace 2    # Print only the top two entries
   backtrace -1   # Print a stack trace except the initial (least recent) call.
   backtrace -s   # show source lines in listing
   backtrace --source   # same as above

See also:
---------

**frame** and  **list**
' 1 _Dbg_complete_backtrace

# Command completion for a frame command
_Dbg_complete_backtrace() {
    typeset -i start=0
    typeset -i end;   ((end=_Dbg_stack_size-1))
    _Dbg_complete_num_range $start $end
}

# FIXME: $1 is a hidden parameter not shown in help. $2 is COUNT.
# $3 is FRAME-INDEX.
function _Dbg_do_backtrace {

    _Dbg_not_running && return 3

    typeset -i show_source=0
    typeset -i OPTLIND=1
    typeset OPTLARG OPTLERR OPTLPENDING opt

    while getopts_long sh opt  \
        source no_argument     \
        help no_argument       \
	'' $@
    do
	case "$opt" in
	    s | source )
		show_source=1
		shift
		;;
	    h | help)
		_Dbg_do_help backtrace
		return
		;;
	esac
    done

    typeset -i count=${1:-$_Dbg_stack_size}
    $(_Dbg_is_int $count) || {
	_Dbg_errmsg "Bad integer COUNT parameter: $count"
	return 1
    }

    typeset -i frame_start=${2:-0}

    if (( count < 0 )) ; then
	(( frame_start = _Dbg_stack_size + count ))
	(( count = _Dbg_stack_size ))
    fi

    $(_Dbg_is_int $frame_start) || {
	_Dbg_errmsg "Bad integer parameter: $ignore_count"
	return 1
    }

    # i is the logical frame value - 0 most recent frame.
    typeset -i i=frame_start
    typeset -li adjusted_pos

    ## DEBUG
    ## typeset -p pos
    ## typeset -p BASH_LINENO
    ## typeset -p BASH_SOURCE
    ## typeset -p FUNCNAME

    typeset    filename
    typeset -i adjusted_pos
    # Position 0 is special in that get the line number not from the
    # stack but ultimately from LINENO which was saved in the hook call.
    if (( frame_start == 0 )) ; then
	((count--)) ;
	adjusted_pos=$(_Dbg_frame_adjusted_pos 0)
	filename="$(_Dbg_resolve_expand_filename "${BASH_SOURCE[$adjusted_pos]}")"
	filename="$(_Dbg_adjust_filename "$filename")"
	_Dbg_frame_print $(_Dbg_frame_prefix 0) '0' '' "$filename" "$_Dbg_frame_last_lineno" ''
    fi

    typeset -i skip_fns
    ((skip_fns=${#FUNCNAME[@]} - _Dbg_stack_size + 1))
    _Dbg_frame_set_fn_param $skip_fns

    # Loop which dumps out stack trace.
    ## DEBUG
    ## typeset -p BASH_ARGC
    ## typeset -p BASH_ARGV
    ## typeset -p FUNCNAME
    ## typeset -p _Dbg_next_argc
    ## typeset -p _Dbg_next_argv
    ## echo "Adjusted pos $(_Dbg_frame_adjusted_pos 0)"

    for ((  i=frame_start+1 ;
	    i <= _Dbg_stack_size && count > 0 ;
	    i++ )) ; do
	typeset -i arg_count=${BASH_ARGC[$_Dbg_next_argc]}
	adjusted_pos=$(_Dbg_frame_adjusted_pos $i)
	_Dbg_msg_nocr $(_Dbg_frame_prefix $i)$i ${FUNCNAME[$adjusted_pos-1]}

	# Print out parameter list.
	if (( 0 != ${#BASH_ARGC[@]} )) ; then
	    _Dbg_frame_fn_param_str
	    if [[ ${FUNCNAME[$adjusted_pos-1]} == "source" ]] ; then
		_Dbg_parm_str=\"$(_Dbg_file_canonic "${BASH_ARGV[$_Dbg_next_argv-1]}")\"
	    fi
	fi

	typeset -l lineno
	if (( adjusted_pos == ${#BASH_SOURCE[@]} )) ; then
	    lineno=0
	    ((adjusted_pos--))
	else
	    lineno=${BASH_LINENO[$adjusted_pos-1]}
	fi
	filename="$(_Dbg_resolve_expand_filename "${BASH_SOURCE[$adjusted_pos]}")"
	filename="$(_Dbg_adjust_filename "$filename")"
	_Dbg_msg "($_Dbg_parm_str) called from file \`$filename'" "at line $lineno"
	if (( show_source )) ; then
	    _Dbg_get_source_line $lineno "${BASH_SOURCE[$adjusted_pos]}"
	    _Dbg_printf "%s" "$_Dbg_source_line"
	fi
	((count--))
    done
    return 0
}

_Dbg_alias_add bt backtrace
_Dbg_alias_add T backtrace
_Dbg_alias_add where backtrace
