SSブログ

Tcl 8.6 のインラインアセンブラを使って簡易言語を作る [Tcl]

Tcl 8.6から::tcl::unsupported::assembleというコマンドでインラインアセンブラが使えるようになっていた。 これを使うとTclの仮想マシンのバイトコードに対するアセンブラをTclコード中に書くことができる。

unsupportedという記載からもわかるように公式なドキュメントはないが、 故意にエラーを出すことによってエラーメッセージから使い方を忖度することができる。

例えば ::tcl::unsupported::assemble help とすると命令の一覧が出てくる。 (helpというサブコマンドがあるわけではなくて、存在しない命令を与えた時のエラー)

% ::tcl::unsupported::assemble help
bad instruction "help": must be push, add, append, appendArray, appendArrayStk, appendStk, arrayExistsImm, arrayExistsStk, arrayMakeImm, arrayMakeStk, beginCatch, bitand, bitnot, bitor, bitxor, concat, coroName, currentNamespace, dictAppend, dictExists, dictExpand, dictGet, dictIncrImm, dictLappend, dictRecombineStk, dictRecombineImm, dictSet, dictUnset, div, dup, endCatch, eq, eval, evalStk, exist, existArray, existArrayStk, existStk, expon, expr, exprStk, ge, gt, incr, incrArray, incrArrayImm, incrArrayStk, incrArrayStkImm, incrImm, incrStk, incrStkImm, infoLevelArgs, infoLevelNumber, invokeStk, jump, jump4, jumpFalse, jumpFalse4, jumpTable, jumpTrue, jumpTrue4, label, land, lappend, lappendArray, lappendArrayStk, lappendStk, le, lindexMulti, list, listConcat, listIn, listIndex, listIndexImm, listLength, listNotIn, load, loadArray, loadArrayStk, loadStk, lor, lsetFlat, lsetList, lshift, lt, mod, mult, neq, nop, not, nsupvar, over, pop, pushReturnCode, pushReturnOpts, pushResult, regexp, resolveCmd, reverse, rshift, store, storeArray, storeArrayStk, storeStk, strcmp, streq, strfind, strindex, strlen, strmap, strmatch, strneq, strrange, strrfind, sub, tclooClass, tclooIsObject, tclooNamespace, tclooSelf, tryCvtToNumeric, uminus, unset, unsetArray, unsetArrayStk, unsetStk, uplus, upvar, variable, verifyDict, or yield

pushという命令について知りたければ同様にエラーを出してみる。

% ::tcl::unsupported::assemble push
wrong # args: should be "push value"

この調子で調べていくと基本的な命令については何となくわかってくる。 そこでTclバイトコードをターゲットとした簡単な言語を作ってみた。

コンパイラはSMLで、パーサジェネレータとしてProglrを使って作る。

以前の記事 をOCaml+BNFC+JavaからSML+Proglr+Tclに変えて行ったものと思えばよい。 ソースコードの全体はGistにアップロードした。

文法定義

文法定義は下記の通り。

token Add "+" ;
token Sub "-" ;
token Mul "*" ;
token Div "/" ;
token LParen "(" ;
token RParen ")" ;
token Eq "=" ;
token Comma "," ;
token Semi ";" ;
token FunKw "fun" ;
token LetKw "let" ;
token InKw "in" ;
token IfKw "if" ;
token ThenKw "then" ;
token ElseKw "else" ;
token Integer of int;
token Ident of string;
token String of string;
 
Grm. Grm ::= [Top] ;
separator Top ";" ;
 
Fun. Top ::= "fun" Ident "(" [Param] ")" "=" Exp ;
Exp. Top ::= Exp ;
 
separator Param "," ;
Param. Param ::= Ident ;
 
Let. Exp ::= "let" Ident "=" Exp "in" Exp ;
Cnd. Exp ::= "if" Exp "then" Exp "else" Exp ;
 
separator Exp "," ;
 
Add. Exp1 ::= Exp1 "+" Exp2 ;
Sub. Exp1 ::= Exp1 "-" Exp2 ;
 
Mul. Exp2 ::= Exp2 "*" Exp3 ;
Div. Exp2 ::= Exp2 "/" Exp3 ;
 
App. Exp3 ::= Ident "(" [Exp] ")" ;
Int. Exp3 ::= Integer ;
Str. Exp3 ::= String ;
Var. Exp3 ::= Ident ;
 
coercions Exp 3;

言語は関数定義と式の連続であり、式中には条件分岐とローカル変数と四則演算と関数呼び出しが書ける。

ProglrはGLRだが、この文法はLALR(1)になっているはずである。 これを確認するには、最初のtokenの行を省くとBNFCの文法定義と互換性があるので、 BNFCに食べさせてocamlyaccに通す。衝突が報告されないことでLALR(1)であることを確認できる。

Proglrでは字句解析についてはml-ulexで行うのだが、割と自明なので掲載を省く。

これをProglrに通すとパーサーと抽象構文木のデータ型のSMLソースコードが生成される。

コンパイラ

メインとなるProglrのドライバは次のように書く。

fun main () =
  let
    val strm = Lexer.streamifyInstream TextIO.stdIn
    val sourcemap = AntlrStreamPos.mkSourcemap ()
    val ast = hd (Parse.parse sourcemap strm)
  in
    check ast;
    compile ast
  end

何故Parse.parseのhdを取っているのかといえば、Parse.parseが構文木のリストを返すからだ。 これはProglrが一般にCFGを扱うからで、CFGの文法は多義的でありうる。

checkは未定義のローカル変数の使用と、ローカル変数の名前の衝突をチェックする。 後者についてはシャドウイングされるものとみなして名前の付け替えをしてもいいと思うが、 今回は単にチェックするだけにした。

fun nameOf (Param (_, name)) = name
 
fun mem (x, []) = false
  | mem (x, y::ys) = x = y orelse mem (x, ys)
 
fun check (Grm (span, tops)) = List.app (fn top => checkTop (top, [])) tops
and checkTop (Fun (span, name, params, body), env) =
      checkExp (body, map nameOf params)
  | checkTop (Exp (span, exp), env) = checkExp (exp, env)
and checkExp (Let (span, name, value, body), env) = (
      checkExp (value, env);
      if mem (name, env) then raise Fail ("dup var: " ^ name)
      else checkExp (body, name::env))
  | checkExp (Cnd (span, cond, t, f), env) =
      (checkExp (cond, env); checkExp (t, env); checkExp (f, env))
  | checkExp (App (span, rator, rands), env) =
      List.app (fn rand => checkExp (rand, env)) rands
  | checkExp (Add (span, e1, e2), env) =
      (checkExp (e1, env); checkExp (e2, env))
  | checkExp (Sub (span, e1, e2), env) =
      (checkExp (e1, env); checkExp (e2, env))
  | checkExp (Mul (span, e1, e2), env) =
      (checkExp (e1, env); checkExp (e2, env))
  | checkExp (Div (span, e1, e2), env) =
      (checkExp (e1, env); checkExp (e2, env))
  | checkExp (Int (span, int), env) = ()
  | checkExp (Str (span, str), env) = ()
  | checkExp (Var (span, var), env) =
      if mem (var, env) then () else raise Fail ("unknown var: " ^ var)

Tclの仮想マシンはJavaVMと同様のスタックマシンである。 したがってコンパイルは前回の記事と大体同様である。

local
    val n = ref 0
in
    fun newLabel () = "label" ^ Int.toString (!n) before n := !n + 1
end
 
fun println s = (print s; print "\n")
 
fun compile (Grm (span, tops)) = List.app compileTop tops
and compileTop (Fun (span, name, params, body)) = (
      println ("proc " ^ name ^ " {" ^ String.concatWith " " (map nameOf params) ^ "} {");
      println ("::tcl::unsupported::assemble {");
      compileExp body;
      println ("}");
      println ("}"))
  | compileTop (Exp (span, exp)) = (
      println ("::tcl::unsupported::assemble {");
      compileExp exp;
      println ("}"))
and compileExp (Let (span, name, value, body)) = (
      compileExp value;
      println ("store " ^ name);
      println "pop";
      compileExp body)
  | compileExp (Cnd (span, cond, t, f)) =
      let
        val falseLabel = newLabel ()
        val trueLabel = newLabel ()
      in
        compileExp cond;
        println ("jumpFalse " ^ falseLabel);
        compileExp t;
        println ("jump " ^ trueLabel);
        println ("label " ^ falseLabel);
        compileExp f;
        println ("label " ^ trueLabel)
      end
  | compileExp (App (span, rator, rands)) = (
       println ("push " ^ rator);
       List.app compileExp rands;
       println ("invokeStk " ^ Int.toString (length rands + 1)))
  | compileExp (Add (span, e1, e2)) = (
      compileExp e1;
      compileExp e2;
      println "add")
  | compileExp (Sub (span, e1, e2)) = (
      compileExp e1;
      compileExp e2;
      println "sub")
  | compileExp (Mul (span, e1, e2)) = (
      compileExp e1;
      compileExp e2;
      println "mult")
  | compileExp (Div (span, e1, e2)) = (
      compileExp e1;
      compileExp e2;
      println "div")
  | compileExp (Int (span, int)) = println ("push " ^ Int.toString int)
  | compileExp (Str (span, str)) = println ("push {" ^ str ^ "}")
  | compileExp (Var (span, var)) = println ("load " ^ var)

実行

コンパイラはtalという実行ファイルになるようにした。 Tcl上での実行を簡単にするためにシェルスクリプトtalexecを書く。

#/bin/sh
 
TEMP=$(mktemp)
tal < $1 > $TEMP
tclsh $TEMP
rm -f $TEMP

ソースファイルを次のように書くと、

fun f(x) =
  if x then x * f(x - 1)
  else 1;
 
puts(f(10))

実行結果はこうなる。

$ talexec fact.tal
3628800

ここでf関数はTclのプロシージャとして定義される。 putsはTclの組み込みのコマンドである。 関数呼び出しの構文で任意のTclコマンドを呼び出すことができる。

使用した命令

命令 説明
push value valueをスタックに積む
pop スタックから1つ取り出す
store varname スタック最上位の値を変数varnameに格納する。スタックは変更されないので注意。
load varname 変数varnameの値をスタックにロードする
jumpFalse label スタック最上位の値が偽のときlabelにジャンプする
jump label 無条件ジャンプ
label name ラベルの定義
invokeStk count スタックの内容でプロシージャ(コマンド)を呼び出す。countは引数の数+1
add スタックの2要素を取り出し、和をスタックに置く
sub スタックの2要素を取り出し、差をスタックに置く
mult スタックの2要素を取り出し、積をスタックに置く
div スタックの2要素を取り出し、商をスタックに置く

感想

今のところTclのVMに関してあまり特殊なことや優位性があるようには思われないので、 一般的な言語処理系のターゲットとする価値があるかというとなさそうである。 アセンブリにオリジナルソースファイルの行番号を埋め込む方法が無いようである点も不利である。

しかしTclの中でDSLをコンパイルして使いたかったり、 Tclと何らかの密な連携を必要とする場合はインラインアセンブラを使う手もあるだろう。


人間にとって自然なソート [Tcl]

ファイル名の連番で "z100.html" が "z2.html" よりも前に来るみたいな問題を解決するためのソートアルゴリズムをいろんな言語で実装したページ [1] があった。最近の Windows のエクスプローラでも自然な順序でソートしてくれる(いつのまにかだけどいつからだろう?)。

これって Tcl だと以下のように lsort のオプションで一発なんです。

set l [list "1000X Radonius Maximus" "10X Radonius" "200X Radonius" "20X Radonius" "20X Radonius Prime" "30X Radonius" "40X Radonius" "Allegia 50 Clasteron" "Allegia 500 Clasteron" "Allegia 51 Clasteron" "Allegia 51B Clasteron" "Allegia 52 Clasteron" "Allegia 60 Clasteron" "Alpha 100" "Alpha 2" "Alpha 200" "Alpha 2A" "Alpha 2A-8000" "Alpha 2A-900" "Callisto Morphamax" "Callisto Morphamax 500" "Callisto Morphamax 5000" "Callisto Morphamax 600" "Callisto Morphamax 700" "Callisto Morphamax 7000" "Callisto Morphamax 7000 SE" "Callisto Morphamax 7000 SE2" "QRS-60 Intrinsia Machine" "QRS-60F Intrinsia Machine" "QRS-62 Intrinsia Machine" "QRS-62F Intrinsia Machine" "Xiph Xlater 10000" "Xiph Xlater 2000" "Xiph Xlater 300" "Xiph Xlater 40" "Xiph Xlater 5" "Xiph Xlater 50" い"Xiph Xlater 500" "Xiph Xlater 5000" "Xiph Xlater 58"]
foreach i [lsort -dictionary $l] {
  puts $i
}

実行結果:

D:\>tclsh alphanum.tcl
10X Radonius
20X Radonius
20X Radonius Prime
30X Radonius
40X Radonius
200X Radonius
1000X Radonius Maximus
Allegia 50 Clasteron
Allegia 51 Clasteron
Allegia 51B Clasteron
Allegia 52 Clasteron
Allegia 60 Clasteron
Allegia 500 Clasteron
Alpha 2
Alpha 2A
Alpha 2A-900
Alpha 2A-8000
Alpha 100
Alpha 200
Callisto Morphamax
Callisto Morphamax 500
Callisto Morphamax 600
Callisto Morphamax 700
Callisto Morphamax 5000
Callisto Morphamax 7000
Callisto Morphamax 7000 SE
Callisto Morphamax 7000 SE2
QRS-60 Intrinsia Machine
QRS-60F Intrinsia Machine
QRS-62 Intrinsia Machine
QRS-62F Intrinsia Machine
Xiph Xlater 5
Xiph Xlater 40
Xiph Xlater 50
Xiph Xlater 58
Xiph Xlater 300
Xiph Xlater 500
Xiph Xlater 2000
Xiph Xlater 5000
Xiph Xlater 10000

Perl や Python の実装が用意されてるってことはこれらの言語にはこの機能はないってことなんだろうな。Perl にはあってもおかしくなさそうだとおもったけど。

以上 Tcl 自慢ができて満足でした。

[1] http://www.davekoelle.com/alphanum.html


Sun が Ruby でも Python でも JavaScript でもなく Tcl を選んだ理由 [Tcl]

Sun Java System Web Server で新しくスクリプト言語を採用することになったんだけどそこで選んだのは Tcl (Jacl) でした [1] という話。選んだ理由が詳しく述べられている。(あれ、これ去年の記事だ・・・)

以前 IBM の WAS は Jacl から Jython に乗り換えた [2] ということに言及したけど Sun の場合は Jython も選択肢として検討しつつ Jacl に落ち着いたということで興味深い。

ちなみに Tcl は一時期 Sun が開発の中心だったことがあって Jacl/TclBlend もそうだけどバイトコードによる高速化など Tcl の多くの近代化がこの時代の産物なので Tcler は Sun に足を向けては寝られないのである。

[1] http://blogs.sun.com/blue/entry/using_wadm_in_sjswebserver_7
[2] http://blog.so-net.ne.jp/rainyday/2007-06-16


IBM 製の Tcl → Python 変換ツール [Tcl]

偶然に表題の Jacl2Jython [1] というツールを見つけた。これはいままで WAS のスクリプティングに Jacl (Tcl) を使っていたのが廃止されて Jython (Python) を採用することになったのでその移行を支援するためのものらしい。Tcl の不採用がさびしい感じだけどツールとしては面白いので使ってみた。

簡単な以下のようなサンプルが、

set var "Hello"

proc greeting {msg} {
  puts $msg
}

greeting $var

for {set i 0} {$i < 10} {incr i} {
  if {$i %2 == 0} then {
    puts $i
  }
}

こう変換される。

#
#####################################################################
## NOTE: This code is PRELIMINARY, it requires MANUAL VERIFICATION ##
##                    ***********              ******************* ##
#####################################################################
#

import sys
def wsadminToList(inStr):
        outList=[]
        if (len(inStr)>0 and inStr[0]=='[' and inStr[-1]==']'):
                tmpList = inStr[1:-1].split() #splits space-separated lists,
        else:
                tmpList = inStr.split("\n")   #splits for Windows or Linux
        for item in tmpList:
                item = item.rstrip();         #removes any Windows "\r"
                if (len(item)>0):
                        outList.append(item)
        return outList
#endDef

var = "Hello"

def greeting ( msg ):
        print msg
#endDef 

greeting(var )

i = 0  #forStart
while ( i < 10 ):  #forTest
        if (i % 2 == 0):
                print i
        #endIf 
        i += 1  #forNext
#endWhile  (#endFor)

「#endDef」とか Python をはじめたばかりの頃についつい書いてしまう感じのコメント行がつくのはご愛嬌。

気になるのは明らかにダイレクトに変換できなさそうな eval とか uplevel とかはどうなるのかということだけど、

set script {puts "hello"}
eval $script

上のようなソースは

script = ["puts", "hello"]
eval ( script )

こうなってしまった。uplevel は単にサポートされていない。

あと他にもこまごまとした点で本来の Tcl の文法より制約が多くなっている。proc の引数が1つの場合は {} で囲まなくてもいいのにそれを要求してくるとか、then はキーワードでもなんでもない(Tcl に他言語でいうキーワードは一切存在しない)のに変数名に使えないとか。

しかし厳密な意味では構文木に変換することもできないし、文字列リテラルとコードブロックの区別も不可能な Tcl のプログラムを普通の他言語に変換するというのは相当にハードルの高い問題だというのは確か。Tcl 処理系自身がやっているバイトコードコンパイルは一体どんな方略を取ってるんだろうか。

[1] http://www-1.ibm.com/support/docview.wss?rs=180&uid=swg24012144


picol, a Tcl interpreter in 550 lines of C code [Tcl]

Picol [1] という550行のCプログラムによる Tcl の実装を発見。

以前 OCaml で書いてみた Tcl [2] ももうちょっと対応コマンドを増やしてみようかなあ。

[1] http://antirez.com/page/picol
[2] http://blog.so-net.ne.jp/rainyday/2006-10-21


Jim の紹介と適切なクロージャとは何かについての疑問 [Tcl]

Jim [1][2] という、クロージャを持った Tcl の処理系があった。

それでまず気になるのがクロージャの自由変数の取り扱いなのだけど、Jim ではプロシージャ定義や匿名関数の仮引数のあとに static variable と呼ばれる関数内に閉じた変数を定義する部分があって、そこに初期値を与えなければ定義時点で見えている値で初期化される。クロージャを使ったカウンタはこんな感じになる。(ドットがプロンプトで{>が継続プロンプトです)

. proc make-counter {} {
{>   set x 0
{>   lambda {} x {incr x}
{> }
. set counter [make-counter]
<reference.<functio>.00000000000000000003>
. $counter
1
. $counter
2

別の言い方をすると局所的に束縛されていない変数を自動的に上に上に探していくということはしてくれなくて、自由変数がどれか―この場合は x ―ということを明示的に教えてやらないといけない。
また、先に「初期化される」と書いたのはこの匿名関数が保持する x は make-counter のローカル関数の x とは別のメモリ領域を指すことになるからだ。
これは以下のような例にすると分かる。

. proc make-counter2 {} {
{>   set x 0
{>   list [lambda {} x {incr x}] [lambda {} x {incr x}]
{> }
. foreach {a b} [make-counter2] {}
. $a
1
. $a
2
. $b
1
. $b
2
. $a
3

なお foreach 部分は多重代入のためのトリック。ここでは2つの匿名関数を作っているが、同じ make-counter2 の局所変数の x を元にしてはいるものの、2つのカウンタが別々にカウントアップしていくのがわかる。

これは例えば Common Lisp のクロージャの仕組みとは異なっている。Lisp ではこうだ。

> (defun make-counter2 ()
    (let ((x 0))
      (values
        (function (lambda () (incf x)))
        (function (lambda () (incf x))))))
MAKE-COUNTER2
> (multiple-value-setq (a b) (make-counter2))
#<Closure: #e3729c>
> (funcall a)
1
> (funcall a)
2
> (funcall b)
3
> (funcall b)
4
> (funcall a)
5

ここでも1つの変数を元に2つのカウンタを作っているが、共有のカウンタ値をカウントアップしていく。これは2つの匿名関数が make-counter2 のローカル変数 x への「参照」を保持しているからだ。

では Jim のクロージャはまだ真のクロージャと呼べるものではないのだろうか。

Evolutions of Lua [3] によると Lua 3.1 のクロージャ実装では、やはり外側の変数そのもの(変数の参照)ではなく、その frozen value をクロージャに持たせることで lexical scoping を実現していたという(したがって上記のようなカウンタは作れなかっただろう)。
その実装が批判を受けたことで作者らは "full" lexical scoping の実装を模索したというようなことが書いてある。

しかし Common Lisp も Jim も Lua 3.1 も、もし自由変数が immutable であれば差は生じないはずだ。クロージャの実例としてよく上記のようなカウンタが使われるけど、これはもともと関数型言語らしい例ではない。

だから問題は「副作用が普通な言語において適切なクロージャ実装とは何か」ということになるのだと思う。
もし Common Lisp の方式が適切で Lua 3.1 や Jim がそうでないとしたらそれはどういう根拠によるものなのだろうか。

[1] http://jim.berlios.de/
[2] http://wiki.tcl.tk/jim
[3] http://www.tecgraf.puc-rio.br/~lhf/ftp/doc/hopl.pdf


Tcl のインタラクティブ環境を保存する [Tcl]

Lisp 処理系とか統計処理言語の R とかではインタプリタでひとしきり関数や変数を定義した後で現在の環境を保存し、次回起動の時に同じ時点から再開できるような機能が付いている。これを Tcl で可能にするプロシージャを作ってみた。

proc save {filename} {
  set f [open $filename w]
  foreach var [info globals] {
    if {![uplevel #0 array exists $var]} {
      puts $f "set {$var} {[uplevel #0 set $var]}"
    } else {
      puts $f "array set {$var} {[uplevel #0 array get $var]}"
    }
  }
  foreach fun [info procs] {
    puts $f "proc $fun {[info args $fun]} {[info body $fun]}"
  }
  close $f
}

ここで使用しているポイントとなるコマンドは以下のとおり。

info globals: グローバル変数名の一覧を取得
info procs: プロシージャの一覧を取得
info args: プロシージャの仮引数を取得
info body: プロシージャのコード部分を取得

まず info globals で得た変数名を foreach にかけてそれぞれファイル保存を行う。配列の場合はそのままでは文字列形式にならないので変わりに array get を使ってシリアライズする必要がある。
ここで変数の内容を取得するのに単に set $var や array get $var としては上手くいかない。そうするとプロシージャのローカルのシンボルテーブルにしかアクセスできないからだ。コマンドをグローバル環境で評価するには uplevel #0 を使う。

プロシージャの保存の方は残りの info 系コマンドを使って素直に実装できる。特に説明を要する部分はないと思う。

対応する restore コマンドは作っていない。上記コードの保存形式は Tcl のスクリプトそのものなので source コマンドを使うことでリストアできる。(このやり方だと変数にバイナリが入っていると上手く行かないかもしれないのでそれに対応したい場合はちゃんと restore コマンドを作った方がよいだろう)


TEApot と TEAcup [Tcl]

先日リリースされた ActiveTcl 8.4.14.0 [1] には TEApot Package Management というものが同梱されるようになった。これは ActivePerl でいう PPM みたいな(ネットワーク経由の)インストール支援機構だ。そのクライアントとして teacup というコマンドが用意されている。

自分の作った Tcl ライブラリがこれを使ってインストールできるようになったらいいというわけでマニュアルを調べてみると、サーバ側のライブラリも用意されてるんだけど TCP ベースの独自プロトコルのサーバ(勿論 Tcl で書かれている)を自分で立てないといけないみたいだ。これは自宅サーバでもないと敷居が高い。HTTP ベースなら気軽にできるんだけど。

ところで Tcl の世界には Perl でいう CPAN みたいなライブラリをアーカイブする中心がない。その辺は Tcl コミュニティ内でも指摘する人はたくさんいて [2] 、 TEApot もきっとそういう動きを前進させるものではあるんだけど、ActiveState 自身の TEApot は投稿を受け付けていない。これは残念なことだ。

[1] http://www.activestate.com/Products/ActiveTcl/
[2] http://wiki.tcl.tk/17271


myintcl - Tcl のみで書かれた MySQL インターフェイス [Tcl]

Tcl のみで実装された MySQL インターフェイスの myintcl [1] というのを2年前くらいに作り始めて、ちょっとだけ作ってずっとそのままだったのを思い出したのでちょっと頑張ってコードを書いて、最低限のことができるようになったのでアルファ版としてリリースした。

API は大体 fbsql [2] を参考にしていて、以下のように使う。

package require myintcl
namespace import myintcl::*

sql connect localhost root password test
sql query {insert into address values('Jane Dowe', '123-45 Tokyo, Japan')}
puts "affected rows: [sql numrows]"

foreach row [sql query {select name,address from address}] {
  puts "Name: [lindex $row 0], Address: [lindex $row 1]"
}
puts "selected records: [sql numrows]"

sql disconnect

C ライブラリにバインディングしないでソケット通信だけで MySQL を触るというのは Perl [3] や Ruby [4] でも同様のモジュールがあるが、いずれも日本人が作っている。

作成に際しては Perl 版の実装と [5] のサイトの「MySQLプロトコル詳説 - PerlからみたMySQLの通信プロトコル」の発表スライドを参考にした。ただし後者はもう情報が古くなっているようで実際と一致しない部分もあった。

なお myintcl という名称は同様に pure Tcl で PostgreSQL インターフェイスを実装した pgintcl [6] に倣っている。

日本語についてはまだあまりちゃんと考えて作ってありません。

追記: リリースした tar.gz ファイルが 0 バイトになってしまっていたのを修正しました。

[1] http://sourceforge.jp/projects/myintcl/
[2] http://www.fastbase.co.nz/fbsql/index.html
[3] http://search.cpan.org/~oyama/Net-MySQL-0.09/MySQL.pm
[4] http://www.tmtm.org/ruby/mysql/
[5] http://shibuya.pm.org/blosxom/techtalks/200301.html
[6] http://pgintcl.projects.postgresql.org/


tclperl - Tcl から Perl を使う [Tcl]

Tcl から Perl のコードを呼び出したい事情があるのだけど(そんな事情が普通あるわけないなどと思わないように!)、自分で作る前に調べてみたら tclperl [1] というのがあった。ソースは [2] から、Windows のバイナリは [3] からダウンロードできる。Windows で ActiveTcl と ActivePerl が入っている環境なら Tcl のライブラリフォルダに置くだけなので非常に手軽に試すことができる。

それでこのパッケージのアプローチというのは以下のような感じ。

% package require tclperl
3.2
% set p [perl::interp new]
perl0
% $p eval { sub f { return $_[0]*2 } }
% puts [$p eval { f(123); }]
246

まずインタプリタをインスタンス化してそれを使う。これは確かに実装上は理にかなっているし、もし複数のインタプリタを使い分けたいなんていうことになった場合は有利なやり方なんだけど、あまり好きじゃない。

まずインタプリタを作成するコードを毎回おまじないのように書かないといけない。

それから Perl で何かしようと思う度に明示的にインタプリタが入ったオブジェクト(上記サンプルでいう $p コマンド)を参照しないといけない。このアプローチだと上記の例で言う「$p eval」は省略することができないから Perl を使おうとする度に最低7文字タイプしないといけないわけだ。ソースコードもその分見づらくなる。

では「複数のインタプリタを使い分けたい」という要望がどれくらいあるかというと実際上は殆どないだろうと思う。グローバル変数の衝突を防いだりするなどのメリットが思いつくが、ここで埋め込んでいるのはフルセットのプログラミング言語の処理系であって、そういう仕組みは1つのインタプリタ内でカバーできる。

なので私の Tcl/Lua ブリッジ [4] [5] は単一インタプリタのアプローチを取っている。このほうが対象言語が「手元にある」感じがして良いと思う。

とはいえ我慢ならないというほどの不便ではないので tclperl は使います。

ところでスクリプト言語埋め込みの世界では Lua や Tcl など元々埋め込み用途が想定にある言語は勿論のこと、比較的大きめと思われる Python なんかも結構使われているような感じがあるけど Perl は埋め込み用途で使っているのをそれほど聞かないように思う。なぜだろう。

[1] http://jfontain.free.fr/tclperl.htm
[2] http://jfontain.free.fr/
[3] http://www.ellogon.org/petasis/index.php?option=com_content&task=view&id=27&Itemid=43
[4] http://blog.so-net.ne.jp/rainyday/2006-11-05-1
[5] http://blog.so-net.ne.jp/rainyday/2006-11-14


この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。