# Copyright 2014-2024 Free Software Foundation, Inc.

# 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 3 of the License, 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.  If not, see <http://www.gnu.org/licenses/>.

# Utility procedures, shared between test suite domains.

# A helper procedure to retrieve commands to send to GDB before a program
# is started.

proc gdb_init_commands {} {
    set commands ""
    if [target_info exists gdb_init_command] {
	lappend commands [target_info gdb_init_command]
    }
    if [target_info exists gdb_init_commands] {
	set commands [concat $commands [target_info gdb_init_commands]]
    }
    return $commands
}

# Given an input string, adds backslashes as needed to create a
# regexp that will match the string.

proc string_to_regexp {str} {
    set result $str
    regsub -all {[]?*+.|(){}^$\[\\]} $str {\\&} result
    return $result
}

# Convenience function that calls string_to_regexp for each arg, and
# joins the results using "\r\n".

proc multi_line_string_to_regexp { args } {
    set res [lmap arg $args {string_to_regexp $arg}]
    return [multi_line {*}$res]
}

# Given a list of strings, adds backslashes as needed to each string to
# create a regexp that will match the string, and join the result.

proc string_list_to_regexp { args } {
    set result ""
    foreach arg $args {
	set arg [string_to_regexp $arg]
       append result $arg
    }
    return $result
}

# Wrap STR in an ANSI terminal escape sequences -- one to set the
# style to STYLE, and one to reset the style to the default.  The
# return value is suitable for use as a regular expression.

# STYLE can either be the payload part of an ANSI terminal sequence,
# or a shorthand for one of the gdb standard styles: "file",
# "function", "variable", "address", etc.

proc style {str style} {
    set fg 39
    set bg 49
    set intensity 22
    set reverse 27
    switch -exact -- $style {
	title { set intensity 1 }
	command { set intensity 1 }
	file { set fg 32 }
	function { set fg 33 }
	highlight { set fg 31 }
	variable { set fg 36 }
	address { set fg 34 }
	metadata { set intensity 2 }
	version { set fg 35; set intensity 1 }
	line-number { set intensity 2 }
	none { return $str }
    }
    return "\033\\\[${fg};${bg};${intensity};${reverse}m${str}\033\\\[m"
}

# gdb_get_bp_addr num
#
# Purpose:
#    Get address of a particular breakpoint.
#
# Parameter:
#    The parameter "num" indicates the number of the breakpoint to get.
#    Note that *currently* this parameter must be an integer value.
#    E.g., -1 means that we're gonna get the first internal breakpoint;
#    2 means to get the second user-defined breakpoint.
#
# Return:
#    First address for a particular breakpoint.
#
# TODO:
#    It would be nice if this procedure could accept floating point value.
#    E.g., 'gdb_get_bp_addr 1.2' means to get the address of the second
#    location of breakpoint #1.
#
proc gdb_get_bp_addr { num } {
    gdb_test_multiple "maint info break $num" "find address of specified bp $num" {
	-re -wrap ".*(0x\[0-9a-f\]+).*" {
	    return $expect_out(1,string)
	}
    }
    return ""
}

# Compare the version numbers in L1 to those in L2 using OP, and
# return 1 if the comparison is true.  OP can be "<", "<=", ">", ">=",
# or "==".
# It is ok if the lengths of the lists differ, but note that we have
# "{1} < {1 0}" instead of "{1} == {1 0}".  See also
# gdb.testsuite/version-compare.exp.

proc version_compare { l1 op l2 } {
    switch -exact $op {
	"=="    -
	"<="    -
	"<"     {}

	">=" {
	    # a >= b => b <= a
	    set x $l2
	    set l2 $l1
	    set l1 $x
	    set op "<="
	}

	">" {
	    # a > b => b < a
	    set x $l2
	    set l2 $l1
	    set l1 $x
	    set op "<"
	}

	default { error "unsupported op: $op" }
    }

    # Handle ops < and ==.
    foreach v1 $l1 v2 $l2 {
	if {$v1 == ""} {
	    # This is: "1.2 OP 1.2.1".
	    if {$op != "=="} {
		return 1
	    }
	    return 0
	}
	if {$v2 == ""} {
	    # This is: "1.2.1 OP 1.2".
	    return 0
	}
	if {$v1 == $v2} {
	    continue
	}
	return [expr $v1 $op $v2]
    }

    if {$op == "<"} {
	# They are equal.
	return 0
    }
    return 1
}

# Acquire lock file LOCKFILE.  Tries forever until the lock file is
# successfully created.

proc lock_file_acquire {lockfile} {
    verbose -log "acquiring lock file: $::subdir/${::gdb_test_file_name}.exp"
    while {true} {
	if {![catch {open $lockfile {WRONLY CREAT EXCL}} rc]} {
	    set msg "locked by $::subdir/${::gdb_test_file_name}.exp"
	    verbose -log "lock file: $msg"
	    # For debugging, put info in the lockfile about who owns
	    # it.
	    puts  $rc $msg
	    flush $rc
	    return [list $rc $lockfile]
	}
	after 10
    }
}

# Release a lock file.

proc lock_file_release {info} {
    verbose -log "releasing lock file: $::subdir/${::gdb_test_file_name}.exp"

    if {![catch {fconfigure [lindex $info 0]}]} {
	if {![catch {
	    close [lindex $info 0]
	    file delete -force [lindex $info 1]
	} rc]} {
	    return ""
	} else {
	    return -code error "Error releasing lockfile: '$rc'"
	}
    } else {
	error "invalid lock"
    }
}

# Return directory where we keep lock files.

proc lock_dir {} {
    if { [info exists ::GDB_LOCK_DIR] } {
	# When using check//.
	return $::GDB_LOCK_DIR
    }

    return [make_gdb_parallel_path cache]
}

# Run body under lock LOCK_FILE.

proc with_lock { lock_file body } {
    if {[info exists ::GDB_PARALLEL]} {
	set lock_file [file join [lock_dir] $lock_file]
	set lock_rc [lock_file_acquire $lock_file]
    }

    set code [catch {uplevel 1 $body} result]

    if {[info exists ::GDB_PARALLEL]} {
	lock_file_release $lock_rc
    }

    if {$code == 1} {
	global errorInfo errorCode
	return -code $code -errorinfo $errorInfo -errorcode $errorCode $result
    } else {
	return -code $code $result
    }
}