Tcl/Tk

出典: フリー百科事典『ウィキペディア(Wikipedia)』

Tcl から転送)

Tcl/Tk(ティクル・ティーケーと読む)は、スクリプト言語 Tcl (Tool Command Language)と、そのGUIツールキット Tk (Tool Kit) を指す。非常に強力なGUIツールキットと、シンプルな文法をもつ言語により、GUIツールを素早く作り上げるのに適した強力なスクリプティング環境である。

目次

[編集] 概要

Tcl/Tk は、スクリプト言語TclとGUIツールキットTkからなる 非常に強力なGUIスクリプティング環境である。現在、各種OS(UnixWindowsMacintosh)上で動作する。

Tclは、コマンド行のみで構造化文法をフォローしてしまう非常にシンプルな文法を特徴とする。Tk はクロスプラットフォームなGUI環境としても有名で、Tclに限らず、PerlPythonRuby などからも一般的にTkが利用されている。

他にも Webブラウザ上でTcl/Tkを動作させるプラグイン Tcletなどがある。

[編集] 背景

Tcl が カリフォルニア大学(UC)バークレー校のJ.K.Ousterhout博士により最初に開発されたのは1988年の事である。当時アプリケーションプログラムに組み込まれる拡張用スクリプト言語には標準がなく、アプリケーション毎に独自の言語が実装されていた。そのためアプリケーション使用者はツール毎に異なるスクリプト言語を習得を余儀なくされた。この非効率さを嘆いたOusterhoutは、状況打開するためにUnixアプリケーションにおける標準となる拡張スクリプト言語をデザインしよう考えた。こうして作られたのがTclの始まりである。そのためTclはアプリケーションへの組み込みが容易であることを重視しデザインされた。具体的には処理系をライブラリとして提供することでCで書かれたアプリケーションに容易に組み込めることや、言語構造がシンプルであり、かつ高い拡張性を持つこと、インタプリター言語であることが挙げられる。

Tk はTcl用に開発された、非常に簡単なコードでGUIを作成できるツールキットである。1990年代初頭にTclにバンドルされる形で公開された。AppleHyperCardに触発されて開発されたと言う。当初Tclの有用な活用事例の一つとして紹介されたTkだが、その取り扱いやすさからTcl言語と共に一躍人気に火がつく。Tclは当初の設計意図と異なり、単体のGUIスクリプティング環境として人気を博した。特にTkの人気は高く、TclにとどまらずPerl (Perl/Tk)、Python (Tkinter)、Ruby (Ruby/Tk)など、他の言語でも標準的なGUIキットとしてTkを利用した。

OusterhotがSun Microsystemsに勤めていた1994-1998年はTcl/Tkは同社で開発が進められた。このころのSunはWWWクライアント環境の制覇に向け邁進していた時期であり、Tcl/Tkもその流れの上、その対象領域をWebに広げていく。Webブラウザ上でTk GUIを動作させるプラグインTcletや、ブラウザのスクリプト言語としてのTclの組み込み、国際化対応(内部処理のUnicode化)、インタプリタからバイトコンパイラへの変更による実行速度の大幅な向上など、Tcl/Tkはこの時期機能的にもっとも大きな進歩を果たした。しかし他技術との競合やブラウザでのサポートの薄さなどもあり、WWWを「第二のTk」として人気を拡大することは残念ながら出来なかった。

Ousterhoutの退職に伴い、Tcl/Tkの開発はSunの手を離れた。 2000年からは Tcl/Tkの開発はオープンソースにその場を移し、精力的に開発が続けられている。

2005年現在 Tcl言語は "Tcl/Tk" の知名度とは裏腹にユーザ数は少なく、PerlやPython、Rubyに比べ 若干マイナーと言わざるを得ない。特に国内でのユーザ数は少ない。ただし、EDAツールにおいては標準的なスクリプト言語として広く利用されているほか、電子国土Webシステムにおいても一部でTcl言語が使われている。一方 Tk は、後発の GTK+Qtと並び、Lightweight Languageでの事実上の標準GUIツールキットの一つとなっており、広く利用されている。

[編集] 特徴

Tk については、別項 Tk に譲る。ここでは Tcl 言語の特徴を記す。

Tcl 言語の特徴は一言でいえばなんでも文字列である。 Tclは、

  • コマンド行の順次実行のみのシンプルな文法
  • 首尾一貫したリスト構造

の二つを組み合わせ、非常に小さいルールで広範囲の領域をカバーする点にある。コマンド行はひとつのリストであり、先頭の要素がコマンド、それ以降の要素がコマンドへのアーギュメントとして扱われる。Lisp的な美しさを持つと言えるが、リストの要素分離記号がブランクやタブであるため、両文字が無視される一般のプログラム言語に慣れた人には「とまどい」を与えるかもしれない。

[編集] リスト構造

リストは文字列である。リストを構成する要素はブランクかタブで区切られる。ブランクやタブを要素に含めたい場合には、その要素をブレス({})で挟めば良い。以下の文字列は3つの要素から成るリストである。

 This is {a pen}

[編集] リストの抽出

Tclパーサーがソースコードからコマンド行としてリストを取り出すとき、ひとつのリストの終端は改行コードかセミコロン(;)で判断する。 しかし改行コードやセミコロンがブレスの内側にあれば、それをリストの終端記号とは見なさない。したがって、以下の3行の文字列は3つの要素から成るひとつのリストである。改行コードは3個あるが、最後の改行コードだけがリストの終端記号の役割を果たす。

 if {$a<0} {
   set a 0
 }

上記リストはTclのifコマンドでありC言語のif文に似ている。しかしブレスがリスト記述記号として採用されているので似ているだけである。上記のifコマンドを以下のように改行の位置を変えると、C言語では許されるがTclではifコマンドの引数エラーになる。

 if {$a<0}
 {
   set a 0
 }

Tclパーサーは上記4行の文字列を2つのリストとして認識する。したがって先頭行のifコマンド行はアーギュメントがひとつだけしか与えられていないことになり、引数エラーが発生する。

ここで重要なのは、エラーを返したのはifコマンドであり、Tclパーサーが検出した「文法エラー」ではないということである。Tclには制御文などの「文」はない。分岐や繰り返しなどの実行制御も単にコマンドによって実現されているだけである。「Tclには細かなルールが無い」のであり、そこにはリスト構造と、以下に解説する「特殊記号」しかない。

[編集] ブラケット記号(コマンド置換)

Tclパーサーはリストの先頭要素を常にコマンド名として認識する。それ以外の要素はコマンドに渡すべきアーギュメントとして認識する。しかし、そのアーギュメント要素がブラケット([ ])で挟まれていると、その中身をコマンド行と認識し、それを実行してから本来のアーギュメント値を求めてくれる。これが「コマンド置換」である。

 set a [expr 100*2]

[編集] $記号(変数置換)

Tclパーサーは変数機能の提供によりコマンド間でのデータの受け渡しも扱ってくれる。変数はsetコマンドにより生成される。そして、$記号が先頭に付いた要素を変数名とみなし、その値に要素全体を置換する。これが「変数置換」である。

 set a {This is a pen.}
 puts $a

変数置換はコマンド行の先頭要素に対しても機能する。つまりコマンド名を変数で与えることも可能である。

変数置換は1回しか実行されない。変数置換結果に$文字が含まれていても再置換が試みられることはない。

また、変数置換で得られた文字列にブランクが含まれていても、2つのアーギュメントとしてではなく、ブランクが含まれた1つのアーギュメントとして渡される。アーギュメントの分離(リストの認識)は変数置換前に行われるからである。もし置換結果から改めてアーギュメント分離を行わせたいならevalコマンドを利用する。

[編集] ダブルクォーテーション記号

リスト構造で解説したように、ブレス({})は、改行コードなどの特殊文字の機能を無効化する。この法則はコマンド置換子であるブラケットや、変数置換子である$記号に対しても貫かれる。以下のコードではコマンド置換も変数置換も行われないので"[expr 100*$num] 円" が出力されてしまう。

 puts {[expr 100*$num]}

コマンド置換も変数置換も機能させ、かつブランクを含む文字列をひとつのアーギュメントとしてputsコマンドに渡すには、ブレスの代わりにダブルクォーテーション("")で挟めば良い。

 puts "[expr 100*$num] 円"

num変数に3がセットされていればコマンドの実行結果として

300 円

が出力される。

このように、ダブルクォーテーションの機能はブレス機能とほぼ同等であるが、コマンド置換と変数置換をTclパーサーに許すところが異なる。 ダブルクォーテーションの中でのコマンド置換、変数置換はTclの特長的な機能である。 なお、ダブルクォーテーションとブレスの機能は、それらがリスト要素の先頭と末尾に記述された場合にのみ有効である。下の例では""は文字として扱われる。

 set text 石を投げたら"ゴツン"と音がした

[編集] セミコロン記号(マルチコマンド)

複数のコマンドを1行に記述したい場合はコマンド行をセミコロン(;)で区切れば良い。

 set a 100; set b 200; puts [expr $a * $b]

[編集] #記号(コメント行)

コマンドの位置に # を記述すると行末までコメントと見なされる。「コマンドの位置に」ということが重要であり、以下の最後のコードは#がコマンドの位置にないので誤りである。

 #初期値セット
 # set a 0
 set b 0 ; # 初期値
 set c 0   # 初期値

[編集] バックスラッシュ記号

ひとつのコマンドを複数行で記述したい場合は、行の末尾にバックスラッシュを付ける。

 command $arg1 $arg2 \
          $arg3 $arg4

$文字の前に\を置くと$置換子の機能を抑制して単なる文字として扱わせることができる。

 puts "金額=\$100"

コマンド置換子([ ])、ブレス({})の前に置いた場合も同様である。 基本的にバックスラッシュにはC言語とほぼ同じ機能がある。例えば改行コードは \n と書ける。

[編集] 補足:アーギュメントをブレスで挟むことのもうひとつの意味

Tclパーサーから渡されたアーギュメントがコマンド内部で評価されるか否かは重要である。ここでの評価とはコマンド置換や変数置換のことである。例えば算術演算を行う"expr"コマンドはアーギュメントを内部で評価している。従って以下の2つのコマンドは同じ結果を返す。

 expr $a*$b
 expr {$a*$b}


Tclパーサーが行ってくれずとも、"expr"コマンドが内部で変数置換しているので同じ結果が得られるのである。"if"コマンドや"while"コマンドのアーギュメントも内部で評価が行われる。 一方、"switch"コマンドの第1引数は内部では評価されない。したがって下記の2番目の記述は期待する結果が得られない。

 switch $val {...}
 switch {$val} {...}

もしアーギュメントをコマンド内部で評価してくれるならば、そのアーギュメントはブレス({})で挟んで渡した方が効率が良い。Tclパーサーとコマンドの両方よる多重評価処理を回避できるからである。このような理由により、ifコマンドやwhileコマンドのアーギュメントは常にブレスで挟むべきである。

[編集] コマンドの拡張

コマンドには、Tclパーサーにあらかじめ実装されているベーシックコマンドと、ユーザーにより作成された拡張コマンドがある。ユーザーによる拡張コマンドの実装は簡単である。まず、C/C++などで「コマンド関数」と「登録用関数」を記述し、ダイナミックリンクライブラリに格納する。そしてベーシックコマンド"load"を用いて拡張コマンドを登録する。

loadコマンドのアーギュメントには、「ライブラリファイル名」と「登録用コマンド名」を与える。loadコマンドは、登録用コマンド名から「登録関数」名を求め、これを実行してくれる。例えば"hello"コマンドであれば"Tcl_HelloInit"を実行してくれる(Tcl_*Init)。この「登録関数」の中に本当の登録処理を記述しておく。従って、例えばloadコマンドに渡したコマンド名にライブラリ名の意味を持たせ、「登録関数」の中で複数のコマンドを登録するようなことも可能である。

「本当の」コマンド登録はTcl C/C++インターフェース関数の"Tcl_CreateCommand"やTcl_CreateObjCommand"を用いて行う。拡張コマンドの実体となる「コマンド関数」を、決められた型と引数に従って先に定義しておき、そのコマンド関数アドレスとコマンド名を引数に与えて実行すれば登録される。

「コマンド関数」は、Tclパーサーからのアーギュメントを、引数の数(argc)と引数文字列配列(argv)で受け取る。ただし、アーギュメントを文字列で受け取るこの方式は"Tcl_CreateCommand"関数で登録する場合であり、アーギュメントをTclオブジェクトで受け取りたい場合には"Tcl_CreateObjCommand"関数で登録する。

[編集] 内部構造(Tclオブジェクト)

Tclパーサーは、スクリプト文字列を受け取り、常に処理結果を文字列で返すように見える。これでは文字列の解析や文字コードとバイナリ値への変換が頻繁に行われていることになり、いかにも効率が悪く思える。しかし決してそのような単純なものではなく、内部では可能な限りバイナリ値を維持している。Tclスクリプトでバイナリ値も扱えるのはこのおかげである。

例えば下記のように変数に数値を与えてその計算結果を変数に格納するとき、型変換は変数への値入力のときだけである。1,2行目だけにstrtod()関数による文字列→バイナリ変換が行われる。

 set a 100.0
 set b 200.0
 set c [expr $a * $b]

Tclパーサー内部において変数はTclオブジェクトとして存在している。このオブジェクトはchar *,int,doubleの要素を持った構造体であり、文字列への変換が要求されない限り、int値はint値のまま、double値はdouble値のまま維持される。スクリプトでは変数の型宣言を行えないが、変数への値セットで型が仮定され、Tclオブジェクト間のデータ移動で無駄な型変換が行われないように配慮されている、ということである。

リストも同様に内部ではオブジェクトとして保持されている。スクリプトに記述された文字列によるリストは、Tclオブジェクトのリスト構造として格納されている。このような仕組みになっているので、リストを作成する時はlistコマンドを用いるべきであることが分かる。下記の例では変数listAには文字列として格納されるが、listBにはint値のリストとして格納される。この後、これらの変数にリスト処理コマンドでアクセスすると、listAに対しては要素分解処理が行われるが、listBに対しては不要となる。

 set a 100
 set b 200
 set listA "$a $b"
 set listB [list $a $b]

前項で解説したコマンド登録関数の後者(Tcl_CreateObjCommand)は、Tclパーサーからのアーギュメントを、無駄に文字列変換することなく、Tclオブジェクトのままで受け取るコマンド関数を登録するためのものである。

Tclオブジェクトを操作するコマンド関数は、リターン値もTclオブジェクトで返すように実装する。そのオブジェクトは呼び出し側にて参照後に自動的に削除される。

リターン値のみならず、Tclオブジェクトは不要になったとき自動的に削除される仕組みがある。それはTclオブジェクトが持つ「参照カウンタ」による制御である。誰かが作成したTclオブジェクトでも、それを破棄したければ参照カウンタを減じ、一方、自分の手を離れても存続させたければ増加させておく、というルールを守れば、誰も参照しなくなった(参照カウンタが0)タイミングでのみ削除される仕組みになっている。

変数のみならず、スクリプトもTclオブジェクトとして存在している。

[編集] 外部リンク