HardwareEng.
HardwareEng.

Reputation: 99

Treat argument as string in Tcl 'expr' statement

I'm using the short syntax of 'if' statement like this:

proc WriteParameter {Parameter Value} {
    # Ugly option - WORKS
    if {$Parameter eq "Unique"} {
        set Register ControlStatus
        set Data ${Value}0
        set Mask EF
    } else {
        set Register $Parameter
        set Data $Value
        set Mask {}
    }

    # Elegant option - DOESN'T WORK
    set Register [expr {$Parameter eq "Unique" ? "ControlStatus" : $Parameter}]
    set Data [expr {$Parameter eq "Unique" ? ${Value}0 : $Value}]
    set Mask [expr {$Parameter eq "Unique" ? EF : {}}]

    puts $Register-$Data-$Mask
    return 0
}
set Value 4E20 ;# Merely hexadecimal number
set Parameter Regular
WriteParameter $Parameter $Value

The problem is that in the elegant option, since the 'expr' statement always treats its arguments as integers, 'Data' gets the value of 4e+20, which is merely the scientific notation of 'Value'.

However, I need 'Data' to be 'Value' (for example, to write to an external register).

Any ideas?

Upvotes: 0

Views: 340

Answers (1)

Donal Fellows
Donal Fellows

Reputation: 137577

The expr language is rather different to the rest of Tcl. You have to care much more about syntax there. Putting things in "double quotes" can help.

set Register [expr {$Parameter eq "Unique" ? "ControlStatus" : $Parameter}]
set Data [expr {$Parameter eq "Unique" ? "${Value}0" : $Value}]
set Mask [expr {$Parameter eq "Unique" ? "EF" : {}}]

However, you might instead be better using if as that doesn't try to convert the results of its arms to a number (using approximately the rules for C constants) unlike expr.

set Register [if {$Parameter eq "Unique"} {string cat ControlStatus} {string cat $Parameter}]
set Data [if {$Parameter eq "Unique"} {string cat $Value 0} {string cat $Value}]
set Mask [if {$Parameter eq "Unique"} {string cat EF}]
# We can omit the else clause; the default is an empty string anyway

This depends on string cat, which was introduced in Tcl 8.6.3 (well, 8.6.2 but that had some bugs in it's I/O subsystem that you really want to avoid). If you're using anything from 8.5 up to 8.6.1, use this instead:

set Register [if {$Parameter eq "Unique"} {return -level 0 ControlStatus} {return -level 0 $Parameter}]
set Data [if {$Parameter eq "Unique"} {return -level 0 ${Value}0} {return -level 0 $Value}]
set Mask [if {$Parameter eq "Unique"} {return -level 0 EF}]

Yes, return -level 0 just gives the value as its result. “Obvious and discoverable”…

If you're on 8.4 or before still (Upgrade, man! You're out of security support!) then you need a little helper procedure:

proc value {value} {return $value}
set Register [if {$Parameter eq "Unique"} {value ControlStatus} {value $Parameter}]
set Data [if {$Parameter eq "Unique"} {value $Value 0} {value $Value}]
set Mask [if {$Parameter eq "Unique"} {value EF}]

The value procedure above will work with later versions of Tcl too, but it gives less efficient bytecode.


However, for all the above I'd actually do something different:

set Register $Parameter
set Data $Value
set Mask ""
if {$Parameter eq "Unique"} {
    set Register ControlStatus
    append Data 0
    set Mask EF
}

I might also use scan to parse the value and format to do the composing of the results back to hex (if necessary). Like that, it'd be much easier to think about the value and not just the representation.

Upvotes: 1

Related Questions