TCLとは
TCLは、Tool command languageというインタプリタ言語です。C他の言語で拡張セットを簡単に組み込めます。
大学で、学生の書くプログラムのユーザーインターフェイスの質の向上のために大学の教授がクリスマスの休暇に書いたことから始まりました。
文法パーサの修正なしで拡張ができる代わりに、初心者は微妙な部分で書き方に悩むのが普通です。拡張とは、Cなどの言語で書いたライブラリです。拡張する部分には、forkなどのすべてのシステムコールの呼び出し、デバイスの低レベルの操作、TCL変数へのアクセスなど通常のTCLのスクリプトではできない操作を入れることができます。
初心者だけではなく、エキスパートも時に悩むのは文字列の扱いです。ここで取り上げる3項演算子の限界も文字列に関係します。
TCLでは、文字列の代入は、以下のように引用符なしで書けます。
set a ZZZZ
しかしif文の中では、引用符が要ります。
if {$a == "ZZZZ"} {
このif文の不思議な制約の探求は、開発者が著した教科書的な本にも「要るよ。以上」的な説明があるのであきらめましょう。他にもこのような不思議な制約はあるのでしょうか。もちろんあります。
3項演算子とは
他の言語にもありますが、
a?b:c
のように書くもので、aが真の時には、b, aが偽の場合には、cになります。
多くの言語では、基本的な文法として定義されていますが、TCLでは、expr というコマンドの受け付ける文字列として扱われます。
exprとは
一般的な意味で計算を行うことができるのは、唯一このコマンドだけです。例外は、setコマンドによる代入と、if文の中での論理演算です。Cなどでは、
a = b + c * d ;
のように代入と演算を行う式が書けます。TCLでは、
set a [expr $b+$c*$d]
のように書きます。TCLの場合は、計算をexpr で行い、その結果をsetを使って代入する という形になります。先に行うexprの部分を[]で囲みます。exprの実行によって[expr...]はexprの結果に置き換わります。exprの計算結果が0であれば、0. 1000であれば1000. 文字列であれば、その文字列になります。
set [set t [expr [string length [file extention $fn]]*10]+[
のように[が階層化することもよくあります。一行に圧縮するためには、?:は有効ですが、一行に圧縮するのに手間かけるのは、TCL世界でも一般ウケするものではありません。
じゃ何が問題(おもしろい)か
結論は「期待するように文字列を使うことはできない。」になります。
では、本番に行きましょう。
set temp [ 1 == 2 ? 11 : 99 ]
puts $temp
はプログラムっぽくないのですが問題ないですね。a の値は、99になります。多くの言語では空白は無視されて上のコードは、
set temp [1==2?11:99]
puts $temp
と同じです。変数使ってみます。
set n1 22 ; set n2 33
set temp [expr $n1 == $n2 ? 11 : 99 ]
puts $temp
set c1 11 ; set c2 99
set temp [expr $n1 == $n2 ? $c1 : $c2 ]
puts $temp
少しはプログラムぽくなりました。結果は両方99。
TCLでは変数の型は厳密ではないので、以下のように引用符でくくっても同じ感じに見えます。TCLには、ユーザには識別できない型もありますが、それは別の機会で..
set temp [expr $n1 == $n2 ? "11" : "99" ]
puts $temp
set temp [expr $n1==$n2?"11":"99"]
puts $temp
結果は同じ。99。
型付けが弱いのであれば、これもできそう?
set s1 ii ; set s2 @@
set temp [expr $s1 == $s2 ? $c1 : $c2 ]
puts $temp
しかしこれはエラー
invalid bareword "ii"
in expression "ii == @@ ? 11 : 99";
この部分を理解するのがここの主題で、
- TCL では$varはそのコマンドの実行前に $varの内容に置き換きかわる。
- TCLの基本の単語は、変数名、コマンド名、数字、引用符に囲まれた文字列。
上の規則で、expr $s1 == $2 .. は expr 11 == @@ になり、下の規則に違反するので、invalid ということになります。もしやと思って以下のように直しても全部結果は同じです。
set temp [expr ${s1} == ${s2} ? $c1 : $c2 ]
set temp [expr ($s1 == $s2) ? $c2 : $c2 ]
set temp [expr $s1==$s2?$c1:$c2 ]
もう方法はないのでしょうか?あります。
3項演算子で文字列を使う方法(1)
じゃじゃんー。結果的にはがっかりになります。
set s1 \"ii\" ; set s2 \"@@\"
set temp [expr $s1 == $s2 ? $c1 : $c2]
puts $temp
これでちゃんと、99になります。
じゃや逆に?の後ろはどうでしょう。?の前においても、後ろにおいても同じように置換され、同じような結果になります。で、最後のものだけOKで、
set temp [expr ($n1 == $n2) ? $s1 : $s2 ]
puts $temp
は、
@@
になります。... アレ。微妙に変な気がします。
set s2 @@
set s2 \"@@\"
は同じなんでしょうか?細かく見てみます。
puts $s2
if { [expr ($n1 == $n2) ? $s1 : $s2 ] == $s2 } {
puts "same"
} else {
puts "different"
}
puts [string length $s2]
と試すと、
"@@"
diff
4
となり、違うもので、長さが4であることから\"@@"は、引用符も文字列の一部であることがわかります。exprは自分自身でパーサを持っていると予想されます。それは
puts [expr 1*2]
puts 1*2
を見るとexpr は'1','','2' という3つのトークンを認識し、puts は12という1つだけのトークンを認識しているからです。
puts [expr 2]
puts [expr "2"]
puts [expr 1*2]
puts [expr "1*2"]
puts [expr [expr \"1*2\"]]
puts [expr \"1*2\"]
も引用符で囲まれたものはエラーになってもよさそうですが、最後以外は、全部2になって便利です。最後は引用符が無い"1*2"になり、無駄に苦労したいのであれば、その上のようにできます。
以下はもっと病気の例。全部エラーになります。
expr \"2
expr \'2\'
expr \"\"2\"\"
3項演算子で文字列を使う方法(2)
set s1 ii ; set s2 @@ ; set c1 11 ; set c2 99 ;
set temp [lindex [list $c1 $c2] [expr \$s1 != \$s1]]
puts $temp
おお。この\って一体なんだ。しかしexprが文字列を扱えないわけではないことが分かります。パースに少し癖があるだけ。
3項演算子で文字列を使う方法(3)
?の前に文字列を指定する方法は、以下のかたちでもできます。リストを作ってその要素の一番、2番目にアクセスします。
set s1 ii ; set s2 @@ ; set c1 11 ; set c2 99 ;
set temp [lindex [list $s1 $s2] [expr $c1==$c2?0:1]
puts $temp
3項演算子に何を求めていたのかによりますが、2個からの選択ではなく、もっと沢山からの選択を行う何かもできそうです。
またこの書き方で?にこだわらないのであれば、以下のように少し短くできます。ただし、**==を!=**する必要があります。
set temp [lindex [list $s1 $s2] [expr $c1!=$c2]]
puts $temp
まとめ。
- 本質的にはexprは文字列を扱うことができる。?の前後、どこでも置ける。
- exprは入力引数を独自のパーサで分析する。この時にダブルクオートは取り除かれることがある。全部無条件に取られるわけではない。
- パーサは文字列はダブルクオートで囲まれていると期待しているが、それは正しくない。
- 2番目の項目と3番目の項目の間に一貫性が無い気がする。そのために、exprに文字列を与えるのは避けたほうがいい。
おわり。-gm300