Is there an equivalent to a 'define' or a 'constant' in TCL?
Currently my code has something like this:
set FLAG_1 0x01
set FLAG_2 0x02
set FLAG_x [eval FLAG_1 | FLAG_2]
proc somefunc {bits} {
if { $bits & $::FLAG_1 } { puts "Bit 1 is set" }
if { $bits & $::FLAG_1 } { puts "Bit 2 is set" }
}
This is a bit ugly I think.. having to reference FLAG_1 as $::FLAG_1 or
defining it as global FLAG_1 first.
In C, one would use:
#define FLAG_1 0x01
#define FLAG_2 0x02
#define FLAG_x (FLAG_1 | FLAG_2)
I was wondering if there was a way to define constants or create aliases so
the interpreter can replace them..
(The "Everything Is A String" is not always fun, having to call [expr] on
everything... )
Lisa
Other than "set"? No.
You can create a proc that does a bunch of fancy tricks to simulate
constants, but it's really just sugar coating. The wiki has an example
at [1]
Of course, you always have the option of running your code through cpp
like you do with C code...
I posted once on this topic already, but it got me thinking, and if what
you object to is simply the syntax, you can always redefine the Tcl
syntax to your liking. Specifically, redefine proc to always import
anything defined with 'define' as a global variable, or massage the code
to replace constants with their constant value.
I don't recommend doing this in production code, but I think it makes
for an interesting illustration of how you can tweak the core commands
to do unusual things. This takes the first approach: redefines proc to
create procedures that automatically import variables declared with
"define".
namespace eval ::const {
variable defines
array set defines {}
}
# This must be called to redefine the proc command so constants defined
# by 'define' are automatically imported into procs:
#
proc ::const::init {} {
if {[info commands ::const::orig_proc] == {}} {
rename proc ::const::orig_proc
interp alias {} ::proc {} ::const::cproc
interp alias {} ::define {} ::const::define
}
}
# This is a replacement for 'proc'; it simply adds one line of
# code to the body of the procedure. That one line imports all
# known "defines".
#
proc ::const::cproc {name args body} {
# Don't apply special code to the procs in this namespace...
if {![string match ::const::* $name]} {
set body "[namespace current]::import;\n$body"
}
uplevel 1 [list ::const::orig_proc $name $args $body]
}
# Use this command to define "constants" (loosly speaking)
#
proc ::const::define {name value} {
variable defines
set defines($name) $value
set command [list upvar ::const::defines($name) $name]
if {[info level] == 1} {
uplevel [list set $name $value]
} else {
uplevel [list upvar ::const::defines($name) $name]
}
}
# This imports all known "constants" into the calling context
#
proc ::const::import {} {
variable defines
foreach name [array names defines] {
uplevel [list upvar ::const::defines($name) $name]
}
}
# Example
#
::const::init
define GREETING "Hello, World"
puts "in global scope, GREETING is '$GREETING'"
proc foo {} {
puts "inside foo, GREETING is '$GREETING'"
}
foo
A great question. There is no real equivalent but what I do is this
set const(FLAG_1) 0x01
set const(FLAG_2) 0x02
etc
That way I only have to use "global const" once in the procedure
proc foo { } {
global const
puts "constant FLAG_1 is $const(FLAG_1)"
}
-Wick
why do you want constants oder defines in the first place? In a
compiled language like C++ it makes sense to tell the compiler "this is
a constant" and "if you find a piece of code which tries to modify it
throw me an error". In TCL where everything is modifiable (at least it
appears to me that way) this makes no sense - there is no such checking
done. If you want to avoid that any piece of your code is changing a
certain variable, you could use a procedure:
proc const {c} {
switch -exact -- $c {
CON1 {return 7}
CON2 {return "A"}
default {error "const: invalid constant $c"}
}
}
and whenever you want to use the value in expressions code [const
CON1]. Yes, it will get slow with lots of constants (there is (almost)
no compile time) and it doesn't look to nice. I regularly use a CONST
global array as suggested by wiclwire.
You could use a read-only variable (i.e. one where a write trace undoes
any changes and puts the variable back as it was) as a constant, but it
isn't a technique I'd use myself. Instead, I'd try to write my Tcl to
use the symbols for everything directly as strings; there's no
particular reason to use numbers at all. Indeed, this is what the Tcl
core does in a number of places (e.g. the POSIX flags to the [open]
command work this way.) In turn, instead of writing:
if {$bits & $FLAG_1] { ... }
You'd instead write:
if {"FLAG_1" in $flags} { ... }
or (for people still using 8.4):
if {[lsearch -exact $flags FLAG_1] >= 0} { ... }
This approach also works well with (most) enumerations, when the natural
thing to replace them with is just a collection of legal words. Indeed,
that is what the "subcommands" really are in commands like [string],
[clock], etc.
If you need the numeric forms occasionally, write a helper procedure to
do the conversion:
proc flagsToBits {flags} {
global flagDefinitions
set result 0
foreach f $flags {
set result [expr {$result & $flagDefinitions($f)}]
}
return $result
}
(Well, I'd actually do something with dictionaries and aliases, but
that's getting much more elaborate and beyond a demonstration. ;-))
Donal.
Thanks.
"Torsten Edler" <Nite4...@aol.com> wrote in message
news:1136654200.1...@g43g2000cwa.googlegroups.com...
Lisa
"Donal K. Fellows" <donal.k...@manchester.ac.uk> wrote in message
news:dptqcq$e2t$1...@godfrey.mcc.ac.uk...
It is cleaner most of the time. See:
foobar {FLAG_1 FLAG_2}
vs.
foobar [expr {$::FLAG_1 | $::FLAG_2}]
It's just that the parsing isn't quite so neat. Some you win, some you lose.
> I wanted to type less, not more.. :) Actually, I am fine with what I got
> now, just wondered if there was such thing as a define. That's all.
The answer is "no, but you do things differently in Tcl; it's not C".
> I do like "if {"FLAG_1" in $flags}" though.. the 'in' operand seems very useful.
> Unfortunately I got 8.4, .. won't upgrade unless and updated Debian package
> is made available.
Understood. I'm just going to encourage you to switch once 8.5 becomes
more production-ready than it is now. :-)
Donal.
Hey, I didn't think of this.. you demonstrated the parsing side (the ugly
side), not the pretty side.. indeed it would make it cleaner to pass flag
parameters. I'm going to consider it.
> Understood. I'm just going to encourage you to switch once 8.5 becomes
> more production-ready than it is now. :-)
Don't encourage me.. encourage the Debian package maintainer, so the rest of
the communicate can simpley do an "apt-get update." :)
Lisa
Not yet. 8.5 is still only part-baked and so only suitable for strictly
experimental use (and I'm majorly responsible for that status too. Sorry.)
Donal.
proc def {name = args} {
interp alias {} $name {} expr $args
}
> Currently my code has something like this:
>
> set FLAG_1 0x01
> set FLAG_2 0x02
> set FLAG_x [eval FLAG_1 | FLAG_2]
def FLAG_1 = 0x01
def FLAG_2 = 0x02
def FLAG_x = [FLAG_1] | [FLAG_2]
>
> proc somefunc {bits} {
> if { $bits & $::FLAG_1 } { puts "Bit 1 is set" }
> if { $bits & $::FLAG_1 } { puts "Bit 2 is set" }
> }
proc somefunc {bits} {
if {$bits & [FLAG_1]} { ... }
...
}
>
> (The "Everything Is A String" is not always fun, ... )
Is too! :-)
-- Neil
Should have mentioned that this is a lazy define -- it reevaluates the
value of the constant at each go. A stricter version would be:
proc const a { return $a }
proc def {name = args} {
interp alias {} $name {} const [expr $args]
}
-- Neil
if it is only about changing burried
values in a consistent "single lever" way
what about having a custom proc for
this:
set ::macros [ list \
PI 3.1415 \
MIN 11 \
MAX 100 \
AVRG 55 \
INFO {[lindex $args 2]} \
]
proc mproc {name argx body} {
set mbody [ string map $::macros $body ]
return [ proc $name $argx $mbody ]
}
mproc myproc args {
set v1 PI
set v2 MIN
set v3 MAX
set v4 AVRG
puts stderr "INFO : v1:$v1 v2:$v2 v3:$v3 v4:$v4"
}
myproc StringA StringB StringC StringD
uwe
proc use_defines {} {
foreach {key val} $::defines {uplevel 1 [list set $key $val]}
}
define PI 3.14
define e 2.781
proc try {} {
use_defines
return "PI=$PI, e=$e"
}
93 % try
PI=3.14, e=2.781
what about :
proc defines arg val {
set ::$arg $val
}
proc try {} {
return "PI=$::PI, e=$::e"
}
cost of the string map solution is in app. startup
not at runtime, though the way i wrote it makes [expr]
more expensive. A fix would be:
set ::macros [ list \
PI {$::M(PI)} \
]
set ::M(PI) 3.1415
....
g!
uwe
> Is there an equivalent to a 'define' or a 'constant' in TCL?
> set FLAG_1 0x01
>
> This is a bit ugly I think.. having to reference FLAG_1 as $::FLAG_1 or
> defining it as global FLAG_1 first.
I most vehemently disagree with both those sentiments!
Flagging substitution with a $ gives great clarity over substitution
of bare words.
Declaring globals that enter the local scope allows you to work locally,
on small building blocks, without the worry of accidentally re-using
a global variable name.
> In C, one would use:
> #define FLAG_1 0x01
> I was wondering if there was a way to define constants or create aliases so
> the interpreter can replace them..
Note that these are not even a feature of C, as such, but of the C
preprocessor (cpp) which can be run separately from the C compiler!
Why don't you just run your Tcl program through cpp? (Just kidding;
there are too many syntax conflicts.) The two models for interpretation
of source is so different that the idea of cpp definitions in Tcl doesn't
make sense.
Senseless? Let's go!
In the cpp, the definition of a constant is used when code is parsed,
usually as part of a function. The value where the function is invoked
does not affect anything. Thus one can do
#define A 1
int foo () {... A ...}
#define A 0
int bar () {... A ... foo() ... }
Let's support this style by overloading the proc command. We won't
support substitution in inlined direct code. (Alternatively, we
could overload the source command, which would apply to inlined code
as well, but would not apply defines within the file that they occur.)
Two commands: define and undefine
set _defined_constants() ""
unset _defined_constants()
proc define {var val} {
global _defined_constants
if { ![regexp {^\w+$} $var] } {
return -code error "Illegal name for a defined constant: $var"
}
if { [info exists _defined_constants($var)] } {
return -code error "Constant '$var' was already defined"
}
set _defined_constants($var) $val
}
proc undefine {var} {
global _defined_constants
if { [info exists _defined_constants($var)] } {
unset _defined_constants($var)
}
}
rename proc _def_proc
_def_proc proc {name argslist body} {
foreach {const defin} [array get ::_defined_constants] {
regsub -all \\m${const}\\M $body ${defin} body
}
uplevel 1 [list _def_proc $name $argslist $body]
}
--
Donald Arseneau as...@triumf.ca
array set _defined_constants {}
That seems to convey the intention much clearer in my opinion. Unless of
course I misunderstood the intention in the first place.
Schelte.
--
set Reply-To [string map {nospam schelte} $header(From)]
> On 01/10/06 17:58, Donald Arseneau wrote:
> > set _defined_constants() ""
> > unset _defined_constants()
> >
> Are you trying to create an empty array here? Why not just use:
>
> array set _defined_constants {}
Ensure existence.
Because I wasn't thinking. [array set] is the right way.
--
Donald Arseneau as...@triumf.ca