1# -*- shell-script -*-
2# filecache.sh - cache file information
3#
4#   Copyright (C) 2008-2011, 2013-2015, 2018-2019 Rocky Bernstein
5#   <rocky@gnu.org>
6#
7#   This program is free software; you can redistribute it and/or
8#   modify it under the terms of the GNU General Public License as
9#   published by the Free Software Foundation; either version 2, or
10#   (at your option) any later version.
11#
12#   This program is distributed in the hope that it will be useful,
13#   but WITHOUT ANY WARRANTY; without even the implied warranty of
14#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15#   General Public License for more details.
16#
17#   You should have received a copy of the GNU General Public License along
18#   with this program; see the file COPYING.  If not, write to the Free Software
19#   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA.
20
21typeset _Dbg_bogus_file=' A really bogus file'
22
23# Maps a name into its canonic form which can then be looked up in filenames
24typeset -A _Dbg_file2canonic
25_Dbg_file2canonic=()
26
27# Information about a file.
28typeset -A _Dbg_fileinfo
29
30# Keys are the canonic expanded filename. _Dbg_filenames[filename] is
31# name of variable which contains text.
32typeset -A _Dbg_filenames
33
34_Dbg_filecache_reset() {
35    _Dbg_filenames=()
36    _Dbg_fileinfo=()
37    _Dbg_file2canonic=()
38}
39_Dbg_filecache_reset
40
41# Check that line $2 is not greater than the number of lines in
42# file $1
43_Dbg_check_line() {
44    (( $# != 2 )) && return 1
45    typeset -i line_number=$1
46    typeset filename="$2"
47    typeset -i max_line
48    max_line=$(_Dbg_get_maxline "$filename")
49    if (( $? != 0 )) ; then
50	_Dbg_errmsg "internal error getting number of lines in $filename"
51	return 1
52    fi
53
54    if (( line_number >  max_line )) ; then
55	(( _Dbg_set_basename )) && filename=${filename##*/}
56	_Dbg_errmsg "Line $line_number is too large." \
57	    "File $filename has only $max_line lines."
58	return 1
59    fi
60    return 0
61}
62
63# Error message for file not read in
64function _Dbg_file_not_read_in {
65    typeset -r filename=$(_Dbg_adjust_filename "$1")
66    _Dbg_errmsg "File \"$filename\" not found in read-in files."
67    _Dbg_errmsg "See 'info files' for a list of known files and"
68    _Dbg_errmsg "'load' to read in a file."
69}
70
71# Print the maximum line of filename $1. $1 is expected to be
72# read in already and therefore stored in _Dbg_file2canonic.
73function _Dbg_get_maxline {
74    (( $# != 1 )) && return -1
75    _Dbg_set_source_array_var "$1" || return $?
76    typeset -r line_count_cmd="line_count=\${#$_Dbg_source_array_var[@]}"
77    eval $line_count_cmd
78    eval "typeset last_line; last_line=\${${_Dbg_source_array_var}[$line_count]}"
79    # If the file had a final newline the last line of the data read in
80    # is the empty string.  We want to count the last line whether or
81    # not it had a newline.
82    typeset -i last_not_null=0
83    # [[ -z $last_line ]] && last_line_is_null=1 || last_line_is_null=0
84    ((line_count=line_count-last_line_is_null))
85    echo $line_count
86    return $?
87}
88
89# Return text for source line for line $1 of filename $2 in variable
90# $_Dbg_source_line.
91
92# If $2 is omitted, use _Dbg_frame_filename, if $1 is omitted use
93# _Dbg_frame_last_lineno. The return value is put in _Dbg_source_line.
94_Dbg_get_source_line() {
95    typeset -i lineno
96    if (( $# == 0 )); then
97	lineno=$_Dbg_frame_last_lineno
98    else
99	lineno=$1
100	shift
101    fi
102    typeset filename
103    if (( $# == 0 )) ; then
104	filename="$_Dbg_frame_last_filename"
105    else
106	filename="$1"
107    fi
108    _Dbg_readin_if_new "$filename"
109    if [[ -n $_Dbg_set_highlight ]] && [[ -n $_Dbg_highlight_array_var ]]; then
110	eval "typeset -i count=\${#$_Dbg_highlight_array_var[@]}"
111	if (( count  )) ; then
112	    eval "_Dbg_source_line=\${$_Dbg_highlight_array_var[lineno]}"
113	else
114	    eval "_Dbg_source_line=\${$_Dbg_source_array_var[$lineno]}"
115	fi
116    else
117	eval "_Dbg_source_line=\${$_Dbg_source_array_var[$lineno]}"
118    fi
119}
120
121# _Dbg_is_file echoes the full filename if $1 is a filename found in files
122# '' is echo'd if no file found. Return 0 (in $?) if found, 1 if not.
123function _Dbg_is_file {
124  if (( $# == 0 )) ; then
125    _Dbg_errmsg "Internal debug error _Dbg_is_file(): null file to find"
126    echo ''
127    return 1
128  fi
129  # first character might be encoded as \057 == '/',
130  # find_file:0:1 == "\" , true story
131  typeset find_file="$(printf "$1")"
132  typeset try_find_file
133
134  if [[ -z $find_file ]] ; then
135    _Dbg_errmsg "Internal debug error _Dbg_is_file(): file argument null"
136    echo ''
137    return 1
138  fi
139
140  if [[ ${find_file:0:1} == '/' ]] ; then
141      # Absolute file name
142      try_find_file=$(_Dbg_expand_filename "$find_file")
143      if [[ -n ${_Dbg_filenames[$try_find_file]} ]] ; then
144	  echo "$try_find_file"
145	  return 0
146      fi
147  elif [[ ${find_file:0:1} == '.' ]] ; then
148      # Relative file name
149      try_find_file=$(_Dbg_expand_filename "${_Dbg_init_cwd}/$find_file")
150      # FIXME: turn into common subroutine
151      if [[ -n ${_Dbg_filenames[$try_find_file]} ]] ; then
152	  echo "$try_find_file"
153	  return 0
154      fi
155  else
156    # Resolve file using _Dbg_dir
157    typeset -i n=${#_Dbg_dir[@]}
158    typeset -i i
159    for (( i=0 ; i < n; i++ )) ; do
160      typeset basename="${_Dbg_dir[i]}"
161      if [[  $basename == '\$cdir' ]] ; then
162	basename=$_Dbg_cdir
163      elif [[ $basename == '\$cwd' ]] ; then
164	basename=$(pwd)
165      fi
166      try_find_file="$basename/$find_file"
167      if [[ -f "$try_find_file" ]] ; then
168	  echo "$try_find_file"
169	  return 0
170      fi
171    done
172  fi
173  echo ''
174  return 1
175}
176
177# Read $1 into _Dbg_source_*n* array where *n* is an entry in
178# _Dbg_filenames.  Variable _Dbg_source_array_var will be set to
179# _Dbg_source_*n* and filename will be saved in array
180# _Dbg_filenames. fullname is set to the expanded filename
181# 0 is returned if everything went ok.
182function _Dbg_readin {
183    typeset filename
184    if (($# != 0)) ; then
185	filename="$1"
186    else
187	_Dbg_frame_file
188	filename="$_Dbg_frame_filename"
189    fi
190
191    typeset -i line_count=0
192
193    typeset -i next;
194    next=${#_Dbg_filenames[@]}
195    _Dbg_source_array_var="_Dbg_source_${next}"
196    if [[ -n $_Dbg_set_highlight ]] ; then
197	_Dbg_highlight_array_var="_Dbg_highlight_${next}"
198    fi
199
200    typeset filevar
201    typeset source_array
202    typeset -ri NOT_SMALLFILE=1000
203
204    if [[ -z "$filename" ]] || [[ "$filename" == "$_Dbg_bogus_file" ]] ; then
205	  eval "${_Dbg_source_array_var}[0]=\"$Dbg_EXECUTION_STRING\""
206    else
207	fullname=$(_Dbg_resolve_expand_filename "$filename")
208	if [[ -r "$fullname" ]] ; then
209	    typeset -r progress_prefix="Reading $filename"
210	    _Dbg_file2canonic["$filename"]="$fullname"
211	    _Dbg_file2canonic["$fullname"]="$fullname"
212	    # Use readarray which speeds up reading greatly.
213	    typeset -ri BIGFILE=30000
214	    if wc -l < /dev/null >/dev/null 2>&1 ; then
215		line_count=$(wc -l < "${fullname}")
216		if (( line_count >= NOT_SMALLFILE )) ; then
217		    _Dbg_msg_nocr "${progress_prefix} "
218		fi
219	    fi
220	    builtin mapfile -t -O 1 -c $BIGFILE \
221		-C "_Dbg_progess_show \"${progress_prefix}\" ${line_count}" \
222		$_Dbg_source_array_var < "$fullname"
223	    if [[ -n $_Dbg_set_highlight ]] ; then
224		opts="--bg=${_Dbg_set_highlight}"
225		if [[ -n $_Dbg_set_style ]] ; then
226		    opts="--style=${_Dbg_set_style}"
227		fi
228		highlight_cmd="${_Dbg_libdir}/lib/term-highlight.py $opts $fullname"
229		tempfile=$($highlight_cmd 2>/dev/null)
230		if (( 0  == $? )) ; then
231		    builtin readarray -t -O 1 -c $BIGFILE \
232			-C "_Dbg_progess_show \"${progress_prefix}\" ${line_count}" \
233			$_Dbg_highlight_array_var < "$tempfile"
234		fi
235		[[ -r "$tempfile" ]] && rm "$tempfile"
236	    fi
237	    (( line_count > BIGFILE)) && _Dbg_progess_done
238	else
239	    return 1
240	fi
241    fi
242
243    (( line_count >= NOT_SMALLFILE )) && _Dbg_msg "done."
244
245    # Add $filename to list of all filenames
246    _Dbg_filenames["$fullname"]=$_Dbg_source_array_var;
247    return 0
248}
249
250# Read in file $1 unless it has already been read in.
251# 0 is returned if everything went ok.
252_Dbg_readin_if_new() {
253    (( $# != 1 )) && return 1
254    typeset filename="$1"
255    _Dbg_set_source_array_var "$filename"
256    if [[ -z "$fullname" ]] ; then
257	_Dbg_readin "$filename"
258	typeset rc=$?
259	set +xv
260	(( $? != 0 )) && return $rc
261	[[ -z "$fullname" ]] && return 1
262	_Dbg_set_source_array_var "$filename" || return $?
263    fi
264    return 0
265}
266
267# Set _Dbg_source_array_var to the variable that contains file lines
268# for $1. Variable "fullname" will contain the expanded full filename for $1.
269# 0 is returned if everything went ok.
270_Dbg_set_source_array_var() {
271    (( $# != 1 )) && return 1
272    typeset filename="$1"
273    [[ -z "$filename" ]] && return 2
274    fullname="${_Dbg_file2canonic["$filename"]}"
275    [[ -z "$fullname" ]] && [[ -n "${_Dbg_filenames["$filename"]}" ]] && {
276	fullname="$filename"
277    }
278    [[ -z "$fullname" ]] && return 2
279    _Dbg_source_array_var=${_Dbg_filenames["$fullname"]}
280    if [[ -n $_Dbg_set_highlight ]] ; then
281	_Dbg_highlight_array_var="${_Dbg_source_array_var/_Dbg_source_/_Dbg_highlight_}"
282    fi
283    _Dbg_source_array_var=${_Dbg_filenames["$fullname"]}
284    [[ -z $_Dbg_source_array_var ]] && return 2
285    return 0
286}
287