Variable assignment inside procedures. Tcl - global-variables

I have this example:
set ns [new Simulator]
set var 0
proc proc1{var}{
set $var 2
}
proc proc2{var}{
puts stdout $var
# I want $var to be 2, but it is 0.
}
$ns at 1 "proc1 $var"
$ns at 5 "proc2 $var"
So, can anyone help me please?

You want to work with the variable itself, not copies of it's contents taken at the time that the timer callbacks were created. In this case, you should not pass the variable in as an argument, but rather refer to the global variable directly. Like this:
# Omitting the setup parts that are identical...
proc proc1 {} {
global var
set var 2
}
proc proc2 {} {
global var
puts stdout $var
}
$ns at 1 "proc1"
$ns at 5 "proc2"
If you don't say global (or one of the other commands for accessing out of the scope, such as variable or upvar) the variable that you work with will be purely local to the procedure's stack frame.

Related

how to avoid tSQLt to abort the called proc after raiserror?

In our stored procedures , we like to "return" a specific return value after raising and error using raiserror()
For that to work, we use low severity levels (eg 12), followed by a return value, such as:
create or alter proc dbo.usp_test
as
begin
print '**start of proc'
raiserror( 'error on purpose',12,1);
print '**after the error'
return -3
end
This work perfectly when running that procedure:
declare #RC int
exec #RC = dbo.usp_test
print concat('The return code is ', #RC)
/* output:
**start of proc
Msg 50000, Level 12, State 1, Procedure usp_test, Line 6 [Batch Start Line 12]
error on purpose
**after the error
The return code is -3
*/
However, when that proc is called from a unit test, the behaviour is different, suddenly the execution is stopped after the raiserror:
create schema T_usp_test authorization dbo;
GO
EXECUTE sp_addextendedproperty #name = 'tSQLt.TestClass', #value = 1, #level0type = 'SCHEMA', #level0name = 'T_usp_test'
GO
create or alter proc T_usp_test.[test mytest]
as
begin
exec tSQLt.ExpectException;
declare #RC int;
exec #RC = dbo.usp_test;
print concat('The return code is ', #RC)
end
GO
exec tSQLt.Run 'T_usp_test.[test mytest]'
/* output:
(1 row affected)
**start of proc
+----------------------+
|Test Execution Summary|
+----------------------+
|No|Test Case Name |Dur(ms)|Result |
+--+--------------------------+-------+-------+
|1 |[T_usp_test].[test mytest]| 7|Success|
-----------------------------------------------------------------------------
Test Case Summary: 1 test case(s) executed, 1 succeeded, 0 failed, 0 errored.
-----------------------------------------------------------------------------
*/
So the question:
1) why is the behaviour different and is the proc now suddenly stopping executing at raiserror() ?
2) how can I overcome this ?
The core proc that runs the test is tSQLt.Private_RunTest which runs the test in a TRY...CATCH block. From the docs at https://learn.microsoft.com/en-us/sql/t-sql/language-elements/try-catch-transact-sql?view=sql-server-2017 :
A TRY...CATCH construct catches all execution errors that have a severity higher than 10 that do not close the database connection.
It looks like you've set the severity to 12. Consider lowering the severity of your RAISERROR call and see if that changes the behavior. You could also try wrapping your RAISERROR call in its own TRY...CATCH block.

How to set default value for a proc argument to be a new dictionary in Tcl

I would like to write a proc which can accept dict as an optional argument or create new if none was given. My attempt, however, fails with
too many fields in argument specifier "dictionary [dict create]"
proc getOpts { {dictionary [dict create]} } {
upvar $dictionary dct
for { set i 0 } { $i < $::argc } { incr i } {
set key [lindex $::argv $i]
if { [string index $key 0] == "-" } {
incr i
dict set dct [string trimleft $key "-"] [lindex $::argv $i]
} else {
dict set dct $key ""
}
}
}
As dictionaries are pure values, I thought this could work. It looks I might have been wrong. Still, I would like to know why it doesn’t work.
The reason it does not work is that the argument variable list is a true list, not a command evaluation context. That means the [dict create] is getting parsed as two separate words, and proc doesn't like that. Fortunately, a dict create with no other arguments is just another way of saying the empty string: use {} or "" instead.
The bigger problem is that you are using that with upvar; you probably don't want to do that. The right approach is likely to be to not do the variable binding, and instead to just return the dictionary from the procedure: the caller can save it in one of its own variables if it cares to.

How to move the mouse every 3 minutes?

I am trying to move the mouse every 2 minutes so that the session doesn't time out. But despite no syntax errors, it doesn't work.
My code:
global $x = 1
global $y = 1
If Mod(#MIN, 3) = 0 Then
MouseMove (global $x, global $y, 2)
global $x++
global $y++
endif
Its more usefull to perform a callback function for timed calls.
AdlibRegister('_MouseMove', 2000*60) ; calls the function every 2000*60 ms
OnAutoItExitRegister('_UnRegister') ; unregister the callback function when the script ends
Func _MouseMove()
Local $aPos = MouseGetPos()
; move 1px right and back after a short brake - so that your interface can detect the movement
MouseMove($aPos[0]+1, $aPos[1])
Sleep(50)
MouseMove($aPos[0], $aPos[1])
EndFunc
Func _UnRegister()
AdlibUnRegister('_MouseMove')
EndFunc
Btw.: Increasing values with AutoIt works so
$x += 1
Edit:
I'm not sure, if you want 2 or 3 minutes (you've written both). So you can change it in the time parameter in AdlibRegister(). The interval must given in ms.
When you run your script in SciTE you should see the following error message:
You need the global keyword only when declaring a variable. When using a variable you have to ommit the global keyword. You should change your script accordingly and it might work then.
The following script move the mouse one pixel every 3 minutes, preventing the session time out, with minimal impact on the computer usage.
HotKeySet( "{ESC}" , "Sair")
While True
MouseMove(MouseGetPos(0)+1,MouseGetPos(1))
Sleep(180000)
MouseMove(MouseGetPos(0)-1,MouseGetPos(1))
Sleep(180000)
WEnd
Func Sair()
Exit
EndFunc

Add value to dictionary key only if it is not already present

I find that I am often wanting to append values to dictionary lists, but only if the value is not in the list already. Therefore, I'm trying to separate this out into a procedure, and I can't figure out why the following code doesn't achieve this:
proc DictAdd {_dictionary key value} {
upvar 1 $_dictionary dictionary
if { $value ni [dict get $dictionary $key] } {
dict lappend dictionary $key $value
}
}
Calling this procedure returns the following error:
can't read "dictionary": no such variable
while executing
"dict get $dictionary $key"
(procedure "DictAdd" line 5)
invoked from within
"DictAdd $files baseline $fileName"
(procedure "getFilesToLoad" line 53)
invoked from within
...
Could someone please tell me what I'm doing wrong here? Thanks.
In the invocation
DictAdd $files baseline $fileName
$files is the value of your dictionary, but DictAdd expects the name of the dictionary variable. If you instead invoke it like this:
DictAdd files baseline $fileName
the command works as designed.
BTW: if you define DictAdd like this:
proc DictAdd {_dictionary key value} {
upvar 1 $_dictionary dictionary
if {[dict exists $dictionary $key]} {
dict with dictionary {
upvar 0 $key values
if {$value ni $values} {
lappend values $value
}
}
} else {
dict lappend dictionary $key $value
}
}
you don't get an error message if the key is missing (it adds the value under that key) (the dictionary still needs to exist outside DictAdd) and the checking/adding code gets a little less messy.
Why the name? It’s because of how upvar works. The command takes a stack level (in this case 1 = the caller’s level) and a variable name (contained in _dictionary; “files” in this case); using those it locates a variable outside the executing command, and creates a local alias inside the executing command (in this case named dictionary: files outside is now basically the same variable as dictionary inside). If you pass something else, e.g. the value of files, say {baseline {a b c}}, upvar will look for a variable called {baseline {a b c}} and most likely not find it. It will create the alias anyway, and if you initialize it, a variable called {baseline {a b c}} at the caller’s level will actually be created. But, again, you will want to use the name of the variable (of course, the name of the variable could be the value of another variable when you invoke the command…).
Documentation: dict, if, lappend, ni operator, proc, upvar
The problem is most likely that the dictionary variable that you are referring to is actually unset, and so impossible to read. Try this:
proc DictAdd {_dictionary key value} {
upvar 1 $_dictionary dictionary
if {![info exists dictionary]} {
set dictionary [dict create $key [list $value]]
} elseif {$value ni [dict get $dictionary $key]} {
dict lappend dictionary $key $value
}
}

Is there a way to make a dictionary global in TCL 8.4

I build a dictionary in tcl 8.4 inside a procedure. How do I use the constructed dictionary in another procedure. I have added a sample code of how I construct a dictionary in tcl 8.4. I know tcl 8.5 has in built in 'dict' option, but I have to use tcl 8.4.
proc a {} {
set test(key1) "value1"
set test(key2) "value2"
lappend keylist "key1"
lappend keylist "key2"
foreach key $keylist {
puts "value of $key is $test($key)"
}
}
So the procedure mentioned above builds a dictionary. But since tcl 8.4 interpreter interprets every line "$test($key)" as a separate variable how can I make it global so that I can use it in another procedure.
You can use the global command to make a variable or array a global one.
For instance:
proc a {} {
global test
set test(key1) "value1"
set test(key2) "value2"
lappend keylist "key1"
lappend keylist "key2"
foreach key $keylist {
puts "value of $key is $test($key)"
}
}
a
# Outputs
# value of key1 is value1
# value of key2 is value2
# Now use them in another proc...
prob b {} {
global test
puts $test(key1)
puts $test(key2)
}
b
# Outputs
# value1
# value2
If you are able to return the dictionary, you could pass it in to another proc as a parameter. (Would have preferred to put this in the comment section, but I do not have the required rep)

Resources