Search code examples
cygwintcl

TCL factorial calculation code: extra characters after close-brace


This is the code in TCL that is meant to produce factorial of a number given as parameter by the user.

if {$argc !=1}{
    puts stderr "Error! ns called with wrong number of arguments! ($argc)"
    exit 1
} else 
    set f [lindex $argv 0]



proc Factorial {x}{
for {set result 1} {$x>1}{set x [expr $x - 1]}{
    set result [expr $result * $x]
}
return $result
}
set res [Factorial $f]
puts "Factorial of $f is $res"

There is a similar SO question, but it does not appear to directly address my problem. I have double-checked the code for syntax errors, but it does not compile successfully in Cygwin via tclsh producing the error:

$ tclsh ext1-1.tcl
extra characters after close-brace
    while executing
"if {$argc !=1}{
        puts stderr "Error! ns called with wrong number of arguments! ($argc)"
        exit 1
} else
        set f [lindex $argv 0]



proc Factorial {x}{..."
    (file "ext1-1.tcl" line 3)

TCL Code from: NS Simulator for Beginners, Sophia-Antipolis, 2003-2004


Solution

  • Tcl is a little bit more sensitive about whitespace than most languages (though not as much as, say, Python). For instance, you can't add unescaped newlines except between commands as command separators. Another set of rules are that 1) every command must be written in the same manner as a proper list (where the elements are separated by whitespace) and 2) a command invocation must have exactly the number of arguments that the command definition has specified.

    Since the invocation must look like a proper list, code like

    ... {$x>1}{incr x -1} ...
    

    won't work: a list element that starts with an open brace must end with a matching close brace, and there can't be any text immediately following the close brace that matches the initial open brace. (This sounds more complicated than it is, really.)

    The number-of-arguments requirement means that

    for {set result 1} {$x>1}{incr x -1}{
        set result [expr $result * $x]
    }
    

    won't work because the for command expects four arguments (start test next body) and it's only getting two, start and a mashup of the rest of other three (and actually not even that, since the mashup is illegal).

    To make this work, the arguments need to be separated:

    for {set result 1} {$x>1} {incr x -1} {
        set result [expr {$result * $x}]
    }
    

    Putting in spaces (or tabs, if you want) makes the arguments legal and correct in number.