# vim:se syntax=tcl:

source [file dirname [info script]]/testing.tcl

needs cmd defer
needs cmd interp

test defer-1.1 {defer in proc} {
	set x -
	proc a {} {
		set x +
		# This does nothing since it increments a local variable
		defer {append x L}
		# This increments the global variable
		defer {append ::x G}
		# Will return "-", not "-L" since return happens before defer triggers
		return $x
	}
	list [a] $x
} {+ -G}

test defer-1.2 {set $defer directly} {
	set x -
	proc a {} {
		lappend jim::defer {append ::x a}
		lappend jim::defer {append ::x b}
		return $jim::defer
	}
	list [a] $x
} {{{append ::x a} {append ::x b}} -ba}


test defer-1.3 {unset $defer} {
	set x -
	proc a {} {
		defer {append ::x a}
		# unset, to remove all defer actions
		unset jim::defer
	}
	a
	set x
} {-}

test defer-1.4 {error in defer - error} {
	set x -
	proc a {} {
		# First defer script will not happen because of error in next defer script
		defer {append ::x a}
		# Error ignored because of error from proc
		defer {blah}
		# Last defer script will happen
		defer {append ::x b}
		# This error will take precedence over the error from defer
		error "from a"
	}
	set rc [catch {a} msg]
	list [info ret $rc] $msg $x
} {error {from a} -b}

test defer-1.5 {error in defer - return} {
	set x -
	proc a {} {
		# First defer script will not happen
		defer {append ::x a}
		defer {blah}
		# Last defer script will happen
		defer {append ::x b}
		return 3
	}
	set rc [catch {a} msg]
	list [info ret $rc] $msg $x
} {error {invalid command name "blah"} -b}

test defer-1.6 {error in defer - ok} {
	set x -
	proc a {} {
		# First defer script will not happen
		defer {append ::x a}
		# Error ignored because of error from proc
		defer {blah}
		# Last defer script will happen
		defer {append ::x b}
	}
	set rc [catch {a} msg]
	list [info ret $rc] $msg $x
} {error {invalid command name "blah"} -b}

test defer-1.7 {error in defer - break} {
	set x -
	proc a {} {
		# First defer script will not happen
		defer {append ::x a}
		# This non-zero return code will take precedence over the proc return
		defer {return -code 30 ret30}
		# Last defer script will happen
		defer {append ::x b}

		return -code 20 ret20
	}
	set rc [catch {a} msg]
	list [info ret $rc] $msg $x
} {30 ret30 -b}

test defer-1.8 {error in defer - tailcall} {
	set x -
	proc a {} {
		# This will prevent tailcall from happening
		defer {blah}

		# Tailcall will not happen because of error in defer
		tailcall append ::x a
	}
	set rc [catch {a} msg]
	list [info ret $rc] $msg $x
} {error {invalid command name "blah"} -}

test defer-1.9 {Add to defer in defer body} {
	set x -
	proc a {} {
		defer {
			# Add to defer in defer
			defer {
				# This will do nothing
				error here
			}
		}
		defer {append ::x a}
	}
	a
	set x
} {-a}

test defer-1.10 {Unset defer in defer body} {
	set x -
	proc a {} {
		defer {
			# This will do nothing
			unset -nocomplain jim::defer
		}
		defer {append ::x a}
	}
	a
	set x
} {-a}

test defer-1.11 {defer through tailcall} {
	set x {}
	proc a {} {
		defer {append ::x a}
		b
	}
	proc b {} {
		defer {append ::x b}
		# c will be invoked as through called from a but this
		# won't make any difference for defer
		tailcall c
	}
	proc c {} {
		defer {append ::x c}
	}
	a
	set x
} {bca}

test defer-1.12 {defer in recursive call} {
	set x {}
	proc a {n} {
		# defer happens just before the return, so after the recursive call to a
		defer {lappend ::x $n}
		if {$n > 0} {
			a $($n - 1)
		}
	}
	a 3
	set x
} {0 1 2 3}

test defer-1.13 {defer in recursive tailcall} {
	set x {}
	proc a {n} {
		# defer happens just before the return, so before the tailcall to a
		defer {lappend ::x $n}
		if {$n > 0} {
			tailcall a $($n - 1)
		}
	}
	a 3
	set x
} {3 2 1 0}

test defer-1.14 {defer capture variables} {
	set x {}
	proc a {} {
		set y 1
		# A normal defer will evaluate at the end of the proc, so $y may change
		defer {lappend ::x $y}
		incr y

		# What if we want to capture the value of y here? list will work
		defer [list lappend ::x $y]
		incr y

		# But with multiple statements, list doesn't work, so use a lambda 
		# to capture the value instead
		defer [lambda {} {y} {
			# multi-line script
			lappend ::x $y
		}]
		incr y

		return $y
	}
	list [a] $x
} {4 {3 2 4}}

test defer-2.1 {defer from interp} -body {
	set i [interp]
	# defer needs to have some effect to detect on exit,
	# so write to a file
	file delete defer.tmp
	$i eval {
		defer {
			[open defer.tmp w] puts "leaving child"
		}
	}
	set a [file exists defer.tmp]
	$i delete
	# Now the file should exist
	set f [open defer.tmp]
	$f gets b
	$f close
	list $a $b
} -result {0 {leaving child}} -cleanup {
	file delete defer.tmp
}

testreport
