Tcl
最新版 |
8.6.12[1] / 2021年12月5日 |
---|---|
最新評価版 |
Tcl/Tk 8.7a5[2] / 2021年6月12日 |
リポジトリ | |
対応OS | クロスプラットフォーム |
プラットフォーム | クロスプラットフォーム |
ライセンス | オープンソース |
公式サイト | https://www.tcl.tk/ |
パラダイム | 手続き型 |
---|---|
登場時期 | 1988年 |
設計者 | John Ousterhout |
開発者 | John Ousterhout、Tcl コアチーム |
最新リリース | 8.6[3]/ 2012年7月27日 |
型付け | 動的型付け |
主な処理系 | ActiveTcl |
影響を受けた言語 | AWK、LISP |
影響を与えた言語 | PowerShell[4]、Tea |
ウェブサイト | tcl.sourceforge.net |
拡張子 | .tcl |
背景[編集]
Tclがカリフォルニア大学バークレー校のジョン・ケネス・オースターハウト博士[注釈 1]により最初に開発されたのは1988年の事である。当時アプリケーションプログラムに組み込まれる拡張用スクリプト言語には標準がなく、アプリケーション毎に独自の言語が実装されていた。そのためアプリケーション使用者はツール毎に異なるスクリプト言語の習得を余儀なくされた。この非効率さを嘆いたオースターハウトは、状況を打開するために UNIX アプリケーションにおける標準となる拡張スクリプト言語をデザインしようと考えた。こうして作られたのがTclの始まりである。そのためTclはアプリケーションへの組み込みが容易であることを重視してデザインされた。具体的には処理系をライブラリとして提供することでC言語で書かれたアプリケーションに容易に組み込めることや、言語構造が簡素であり、かつ高い拡張性を持つこと、インタプリタ言語であることが挙げられる。 TkはTcl用に開発された、非常に簡単なコードでGUIを作成できるツールキットである。1990年代初頭にTclにバンドルされる形で公開された。Appleの HyperCardに触発されて開発されたと言う。当初Tclの有用な活用事例の一つとして紹介されたTkだが、その取り扱いやすさからTcl言語と共に一躍人気に火がつく。Tcl は当初の設計意図と異なり、アプリケーションの組み込み言語として使われるよりも、Tkと合わせた﹁Tcl/Tk﹂の形のGUIスクリプティング環境として人気を博した。特にTkの人気は高く、TclにとどまらずPerl︵Perl/Tk︶、Python︵Tkinter︶、Ruby ︵Ruby/Tk︶など、他の言語でも標準的なGUIキットとしてTkが利用された。 オースターハウトがサン・マイクロシステムズに勤めていた1994-1998年はTcl/Tkは同社で開発が進められた。このころのサンは WWW クライアント環境の制覇に向け邁進していた時期であり、Tcl/Tkもその流れの上、その対象領域をウェブに広げていく。ウェブブラウザ上でTk GUIを動作させるプラグイン﹁特徴[編集]
リスト構造[編集]
リストは文字列である。リストを構成する要素はブランクかタブで区切られる。ブランクやタブを要素に含めたい場合には、その要素をブレス︵{
、}
︶で挟めば良い。以下の文字列は三つの要素から成るリストである。
This is {a pen}
リストの抽出[編集]
Tclパーサーがソースコードからコマンド行としてリストを取り出すとき、ひとつのリストの終端は改行コードかセミコロン(;
)で判断する。しかし改行コードやセミコロンがブレスの内側にあれば、それをリストの終端記号とは見なさない。したがって、以下の3行の文字列は3つの要素から成るひとつのリストである。改行コードは3個あるが、最後の改行コードだけがリストの終端記号の役割を果たす。
if {$a<0} {
set a 0
}
上記リストは Tcl の if
コマンドでありC言語の if
文に似ている。しかしブレスがリスト記述記号として採用されているので似ているだけである。上記の if
コマンドを以下のように改行の位置を変えると、if
コマンドの引数エラーになる。
if {$a<0}
{
set a 0
}
if
コマンド行は引数がひとつだけしか与えられていないことになり、引数エラーが発生する。
ここで重要なのは、エラーを返したのは if
コマンドであり、Tclパーサーが検出した﹁文法エラー﹂ではないということである。Tclには制御文などの﹁文﹂はない。分岐や繰り返しなどの実行制御も単にコマンドによって実現されているだけである。﹁Tclには細かなルールが無い﹂のであり、そこにはリスト構造と、以下に解説する﹁特殊記号﹂しかない。
ブラケット記号︵コマンド置換︶[編集]
Tclパーサーはリストの先頭要素を常にコマンド名として認識する。それ以外の要素はコマンドに渡すべき引数として認識する。しかし、その引数要素がブラケット︵[
、]
︶で挟まれていると、その中身をコマンド行と認識し、それを実行してから本来の引数値を求めてくれる。これが﹁コマンド置換﹂である。
set a [expr 100*2]
$
記号(変数置換)[編集]
Tclパーサーは変数機能の提供によりコマンド間でのデータの受け渡しも扱ってくれる。変数はsetコマンドにより生成される。そして、$記号が先頭に付いた要素を変数名とみなし、その値に要素全体を置換する。これが「変数置換」である。
set a {This is a pen.}
puts $a
$
﹂が含まれていても再置換が試みられることはない。
また、変数置換で得られた文字列にブランクが含まれていても、2つの引数としてではなく、ブランクが含まれた1つの引数として渡される。引数の分離︵リストの認識︶は変数置換前に行われるからである。もし置換結果から改めて引数分離を行わせたいなら eval
コマンドを利用する。
ダブルクォーテーション記号[編集]
リスト構造で解説したように、ブレス︵{
、}
︶は、改行コードなどの特殊文字の機能を無効化する。この法則はコマンド置換子であるブラケットや、変数置換子である$記号に対しても貫かれる。そのため、下記のコードではコマンド置換も変数置換も行われない。
puts {[expr 100*$num] 円}
[expr 100*$num] 円コマンド置換も変数置換も機能させ、かつブランクを含む文字列をひとつの引数として
puts
コマンドに渡すには、ブレスの代わりにダブルクォーテーション︵"
︶で挟めば良い。
puts "[expr 100*$num] 円"
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}
expr
コマンドが内部で変数置換しているので同じ結果が得られるのである。if
コマンドやwhile
コマンドは第1引数として与えられた文字列を、コマンド内部で ex
pr
によって評価し、その結果を使う。
set val true
if $val {〜処理〜}
while $val {〜処理〜}
Tclパーサーは第一引数の「$val
」を評価して「true
」(文字列)に書き換える。同様に第二引数「{〜処理〜}」を評価して、文字列リテラルなのでそのまま同じ値「{〜処理〜}」に書き換え、以下のようにする。
if true {〜処理〜}
while true {〜処理〜}
true
と {〜処理〜}
を if
、wh
ile
コマンドに渡し、if
、while
コマンド側は受け取った文字列を expr
コマンドに﹁[expr true]
﹂として渡し、その結果をもって処理を続ける。気をつけなければならないのは wh
ile
コマンドは {〜処理〜}
をひと通り評価し終えた後、再度、第一引数を expr
で評価するが、上記のコードでは与えられている値が true
なので {〜処理〜}
の中に﹁set val
false
﹂のような文があったとしても、第一引数で与えられた値そのものは変化しないので、ループを無限に繰り返すことになる︵{〜処
理〜}
の中に break
コマンドが含まれていない場合︶。
set val true
if {$val} {〜処理〜}
while {$val} {〜処理〜}
{$
val}
﹂という4文字の文字列リテラルを、第二引数として﹁{〜処理〜}﹂を、if
、while
コマンドに渡す。if
、while
コマンド側は受け取った文字列を expr
コマンドに﹁[expr {
$val}]
﹂として渡し、その結果をもって処理を続ける。whil
e
コマンドは {〜処理〜}
をひと通り評価し終えた後、再度、第一引数を expr
で評価するが、上記のコードでは与えられている値が {$val}
なので {〜処理〜}
の中で﹁set val
false
﹂のような文があれば、その評価値も false
になって、その時点で繰り返し処理を終了する。
一方、switch
コマンドの第1引数は内部では評価されない。したがって下記の2番目の記述は期待する結果が得られない。
switch $val {...}
switch {$val} {...}
if
コマンドやwhile
コマンドやfor
コマンドの引数は常にブレスで挟むべきである。特にループの条件式は必ずブレスで挟む必要がある。ブレスで挟まないと変数置換されてからループコマンドに渡されてしまうので、定数を並べた条件式になってしまい、無限ループをもたらす。
コマンドの拡張[編集]
コマンドには、Tclパーサーにあらかじめ実装されているビルトインコマンドと、ユーザーにより作成された拡張コマンドがある。ユーザーによる拡張コマンドの実装は簡単である。まず、C言語などで﹁コマンド本体関数﹂と﹁登録用関数﹂を記述し、ダイナミックリンクライブラリファイルに格納する。そして組み込みコマンドのload
を用いて拡張コマンドを登録する。
load hello.dll hello
load
コマンドの引数には、﹁ライブラリファイル名﹂と﹁登録用コマンド名﹂を与える。load
コマンドは、登録用コマンド名から﹁登録用関数﹂名を求め、これを実行してくれる。例えば hello
コマンドであれば Hello_Init
を実行してくれる。この﹁登録用関数﹂の中に本当の登録処理を記述しておく。従って、例えば load
コマンドに渡したコマンド名にライブラリ名の意味を持たせ、﹁登録用関数﹂の中で複数のコマンドを登録してしまうことも可能である。
﹁本当の﹂コマンド登録はC言語のインターフェース用の関数の Tc
l_CreateCommand
や Tcl_CreateObjC
ommand
を用いて行う。拡張コマンドの実体となる﹁コマンド本体関数﹂を、決められた型と引数に従って先に定義しておき、そのコマンド関数アドレスと公開コマンド名を引数に与えて実行すれば登録される。
コマンド登録関数
int Hello_Init (Tcl_Interp *interp)
{
Tcl_CreateObjCommand (interp, "hello", Tcl_HelloCmd, NULL, NULL) ;
return TCL_OK ;
}
コマンド本体関数
int Tcl_HelloCmd (ClientData dummy, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv [])
{
...
return TCL_OK ;
}
arg
c
︶と引数文字列配列︵argv
︶で受け取る。ただし、引数を文字列で受け取るこの方式は Tcl_CreateCommand
関数で登録する場合であり、引数をTclオブジェクトで受け取りたい場合には、上例のように Tcl_CreateObjCommand
関数で登録する。
内部構造︵Tclオブジェクト︶[編集]
Tclパーサーは、スクリプト文字列を受け取り、常に処理結果を文字列で返すように見える。これでは文字列の解析や文字コードとバイナリ値への変換が頻繁に行われていることになり、いかにも効率が悪く思える。しかし決してそのような単純なものではなく、内部では可能な限りバイナリ値を維持している。Tclスクリプトでバイナリ値も扱えるのはこのおかげである。 例えば下記のように変数に数値を与え、その計算結果を変数に格納するとき、おのおの変数の型は文字列とdouble
の二つの型を持つ。文字列として参照されるときは文字列型として振る舞い、double
として参照されるとき︵計算式の中などで︶は double
型として振る舞うことができる。
set a 100.0
set b 200.0
set c [expr $a * $b]
char *
、int
、double
の共用体を持った構造体であり、文字列への変換が要求されない限り、int
値は int
値のまま、double
値は double
値のまま維持される。スクリプトでは変数の型宣言を行えないが、変数への値セットで型が仮定され、Tclオブジェクト間のデータ移動で無駄な型変換が行われないように配慮されている、ということである。
リストも同様に内部ではリスト型のTclオブジェクトとして存在している。このような仕組みになっているので、リストを作成する時は l
ist
コマンドを用いるべきであることが分かる。また、巨大なリストを文字列として全体参照するのも効率を悪くするので慎重にすべきである。下記の例では変数 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オブジェクトを通じて返すことができる。下記は long
値をそのまま返す例である。
/* long値を返す例 */
Tcl_SetLongObj (Tcl_GetObjResult (interp), longVal) ;
Tcl_Inc
rRefCount
︶、必要なしとなったところで減ずる︵Tcl_D
ecrRefCount
︶ようにする。参照カウンタが減らされて 0
になったときにのみ、そのオブジェクトは削除される。つまり、存続と廃棄の要求を自分だけの都合で出しておくだけで、削除のタイミングがコントロールされるという仕組みである︵スマートポインタ︶。
変数のみならず、スクリプト自体もTclオブジェクトとして存在している。
脚注[編集]
注釈[編集]
出典[編集]
(一)^ “Changes in Tcl/Tk 8.6.12”. 2022年6月20日閲覧。 (二)^ “Tcl/Tk 8.6”. 2022年6月20日閲覧。 (三)^ “Latest Release: Tcl/Tk 8.6.0 (Dec 20, 2012)” (2012年12月20日). 2013年1月4日閲覧。 (四)^ Windows PowerShell : PowerShell and WPF: WTF外部リンク[編集]
- Tcl Developer Exchange - Tcl/Tk開発のホーム
- Tcl 8.4.1 Manual Command Reference - 日本語のTclリファレンス