ネットワークアドレス変換関連の用語

NAT (Network Address Translation)

グローバルIPとローカルIPを1:1対応させる。

NAPT (Network Address Port Translation)

グローバルIPアドレスとポート番号のペアをローカルIPとポート番号のペアに1:1対応させる。

NAPTには多数の同義語が存在する

  • IPマスカレードはNAPTのLinuxにおける実装。
  • NATP(NAT+)、PAT(Port Address Translation)
  • ポートマッピング、ポートフォワーディング
  • 仮想サーバー、バーチャルサーバー、ローカルサーバー
  • NATオーバーロード、オーバーロード変換

静的NAT、静的NAPTは外部から内部のネットワークへのアクセスに使う。一般的なブロードバンドルータの設定に存在するのはこれらの設定。

動的NAT、動的NAPTは内部から外部への通信に対する応答が、送信元に返るようにするための機能であり、通常ユーザーが意識することはない。

と思う。

参考

IPマスカレード(NAPT,PAT,NAT+)を設定する
NAT と IPマスカレード(ポートフォワーディング) について
4.インターネットへの接続形態

Excelオートシェイプでフリーフォームの頂点を直角にする

Excel2007登場以来、なんかいい方法はないものかと探していたけど、ついに見つけた。ありがたや。

図形描画で直線の頂点を編集するには?

Public Sub main() 
    On Error GoTo L_Exit 
    With Selection.ShapeRange.Nodes 
        On Error Resume Next 
        Dim i As Integer 
        For i = 1 To .Count 
            .SetSegmentType i, msoSegmentLine 
            .SetEditingType i, msoEditingCorner 
        Next 
    End With 
L_Exit: 
    On Error GoTo 0 
End Sub

かざぐるマウスからGestures for Chrome (ChromeGesture)に乗り換え。

林檎の木で紹介されてたCloverを入れてみたけど、マウスホイールでスクロールすると無限ループに陥ってしまった。かざぐるマウスを停止すると問題は発生しない。設定でエクスプローラを除外するとか、いろいろやってみたけど効果はなし。
今まで使ったマウスジェスチャソフトで一番気に入っているものだけど、Tcl/Tkとの相性問題というのもあった。
最近はブラウザを使い分けることがほぼなくなったことと、Gestures for Chromeがそろそろ完成度が高まってるのではないかと思い、
ちょっと入れてみました。かざぐるマウスの自分設定と同じにしたものを晒しておきます。
Chrome拡張はchrome:chrome_urlsに対しては無効なので、スタートページでもやはり無効です。
あとページによってはジェスチャの開始点が限定される場合がある。それ以外はそこそこ使えそうなので、しばらくこれで試してみます。

{
  "name": "Chrome Gestures",
  "version": "1.12.1",
  "normal_actions": {
    "L": {
      "name": "back",
      "args": []
    },
    "R": {
      "name": "forward",
      "args": []
    },
    "UL": {
      "name": "select left tab",
      "args": []
    },
    "D": {
      "name": "go to #1",
      "args": [
        "http://www.google.com/",
        "Google"
      ]
    },
    "DR": {
      "name": "close this tab",
      "args": []
    },
    "#FlipBack": {
      "name": "back",
      "args": []
    },
    "#FlipForward": {
      "name": "forward",
      "args": []
    },
    "U": {
      "name": "run script #1",
      "args": [
        "(function(){if((document.fullScreenElement&&document.fullScreenElement!==null)||(!document.mozFullScreen&&!document.webkitIsFullScreen)){if(document.documentElement.requestFullScreen){document.documentElement.requestFullScreen();}else if(document.documentElement.mozRequestFullScreen){document.documentElement.mozRequestFullScreen();}else if(document.documentElement.webkitRequestFullScreen){document.documentElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);}}else{if(document.cancelFullScreen){document.cancelFullScreen();}else if(document.mozCancelFullScreen){document.mozCancelFullScreen();}else if(document.webkitCancelFullScreen){document.webkitCancelFullScreen();}}})()",
        "Fullscreen"
      ]
    },
    "UR": {
      "name": "select right tab",
      "args": []
    },
    "DL": {
      "name": "re-open closed tab",
      "args": []
    }
  },
  "linkdrag_actions": {
    "D": {
      "name": "open in new tab",
      "args": []
    },
    "DU": {
      "name": "open in background tab",
      "args": []
    },
    "U": {
      "name": "copy url",
      "args": []
    },
    "UD": {
      "name": "copy url and text",
      "args": []
    }
  },
  "textdrag_actions": {
    "DR": {
      "name": "search with #1 in new tab",
      "args": [
        "http://www.google.com/search?q=%s",
        "Google"
      ]
    },
    "UL": {
      "name": "copy text",
      "args": []
    },
    "UR": {
      "name": "copy html",
      "args": []
    }
  },
  "mouse_track": true,
  "visualized_arrow": true,
  "superdrag": false,
  "useMousewheel": true,
  "useTabList": false,
  "useSmoothScroll": false,
  "ScrollSpeedValue": 0.2,
  "useScrollAcceleration": true,
  "AccelerationValue": 2,
  "suppress_contextmenu": true,
  "minimumUnit": 10
}

Tcl/Tkで任意のフォルダをデフォルトファイラーで開く

たまにやるけど忘れるので。スペースを含まないときは別のやり方でも開くのですが、
一般的には以下のようにします。cmdの引数にエスケープされた状態で渡すというのがキモです。
string mapの変換ルールではエスケープシーケンスが適用されることに注意します。

set office_dir {C:\Program Files\Microsoft Office}
set escaped_office_dir [string map {\\ \\\\} [file nativename $office_dir]]
puts [concat exec [auto_execok start] explorer $escaped_office_dir]
eval exec [auto_execok start] explorer $escaped_office_dir

-- 追記
上記のスクリプトではエクスプローラで開くのが前提だったので、私のように秀丸ファイラーなどの代替ファイラーを使ってる人にとっては100%満足できるものではありませんでした。なんかこれを書いてるちょっと前にStackOverflowに同じ内容の質問が挙がってて、上記の提案をドヤ顔で投稿したところ、100%満足な回答が返ってきました。
http://bit.ly/Uu6q6N

set folderpath "C:/Program Files/Microsoft Office"
exec {*}[auto_execok start] "" [file nativename $folderpath]

ちなみに、趣旨が異なりますが、エクスプローラで指定のファイルを選択した状態でフォルダを開くには、

set out "C:/Program Files (x86)/Microsoft Office/Office14/EXCEL.EXE"
exec explorer.exe /select, [file nativename $out] &

LinkedList in XOTcl

いわゆる双方向連結リストというデータ構造ですが、かつてはリチャードストールマンに、Tclには構造体がないからlinked listを作れないと指摘されていました。今ではいくつもあるオブジェクト指向拡張を使い、クラスを導入すればTclでも他の言語と同じような形で実装できます。

arrayでもできますが、あまり使いやすくならないと思うので、僕はXOTclでLinkedList::ContainerとLinkedList::Elementというクラスを作っていて、これらを継承するか、mixinしたクラスは、連結リストの管理オブジェクト、リスト要素として扱えるようにしています。

通常のリストに比べると、要素が少ないうちは遅いですが、数千数万の要素数になってくるとかなり効率が違ってきます。

テストスクリプト

package require -exact linkedlist 3.0
 
Class ListContainer -instmixin {LinkedList::Container}
Class ListElement -instmixin {LinkedList::Element}
 
proc test_linkedlist {size} {
	set container [ListContainer new]
	for {set i 0} {$i < $size} {incr i} {
		$container push [ListElement new]
	}
	foreach e [$container rlist] {
		$container delete $e
	}
	return {}
}
 
proc test_nativelist {size} {
	set l {}
	for {set i 0} {$i < $size} {incr i} {
		lappend l $i
	}
	for {set i 0} {$i < $size} {incr i} {
		set l [lreplace $l end end]
	}
	return {}
}
 
foreach size {1000 10000 15000 20000 30000} {
	puts "size=$size"
	puts "linkedlist [time {test_linkedlist $size} 2]"
	puts "nativelist [time {test_nativelist $size} 2]"
	puts ""
}

結果

C:\Tcl\lib\linkedlist3>
tclsh test2.tcl
size=1000
linkedlist 30047.5 microseconds per iteration
nativelist 2168.5 microseconds per iteration
 
size=10000
linkedlist 307218.5 microseconds per iteration
nativelist 215719.0 microseconds per iteration
 
size=15000
linkedlist 491008.5 microseconds per iteration
nativelist 469101.5 microseconds per iteration
 
size=20000
linkedlist 627835.0 microseconds per iteration
nativelist 900121.5 microseconds per iteration
 
size=30000
linkedlist 962158.5 microseconds per iteration
nativelist 1995813.5 microseconds per iteration

tktableなんかもarrayをデータソースにすると高速なんですが、arrayを直接操作するよりも、データの順序などを安全に扱うことができる連結リストでデータ管理するのがいいと思います。

package require XOTcl
namespace import xotcl::*
 
namespace eval LinkedList {}
 
Class LinkedList::Container
 
LinkedList::Container instproc init {} {
	my set first {}
	my set last {}
	my set size 0
	next
}
 
LinkedList::Container instproc destroy {} {
	set es [my list]
	foreach e $es {
		my drop $e
	}
	next
	return $es
}
 
LinkedList::Container instproc clear {} {
	set es [my list]
	foreach e $es {
		$e destroy
	}
	return {}
}
 
LinkedList::Container instproc first {} {
	my instvar first
	return $first
}
 
LinkedList::Container instproc last {} {
	my instvar last
	return $last
}
 
LinkedList::Container instproc size {} {
	my instvar size
	return $size
}
 
LinkedList::Container instproc unlink {element} {
	my instvar size first last
 
	if {![Object isobject $element]} {return -1}
	if {[$element container] != [self]} {return -1}
 
	set prev [$element prev_p]
	set next [$element next_p]
 
	$element prev_p {}
	$element next_p {}
	$element container {}
 
	if {$prev eq {}} {
		set first $next
	} else {
		$prev next_p $next
	}
	if {$next eq {}} {
		set last $prev
	} else {
		$next prev_p $prev
	}
 
	incr size -1
}

# private
LinkedList::Container instproc add {element} {
	my instvar size first last
	if {[$element next_p] == $first} {
		set first $element
	}
	if {[$element prev_p] == $last} {
		set last $element
	}
	$element container [self]
	incr size
	return $element
}
 
LinkedList::Container instproc scan {key value} {
	for {set e [my first]} {$e != {}} {set e [$e next_p]} {
		if {[$e $key] == $value} {return $e}
	}
}
 
LinkedList::Container instproc at {index} {
	if {$index == "end"} {return [my last]}
	if {$index < 0 || [my size] <= $index} {return {}}
	set i 0
	for {set e [my first]} {$e != {}} {set e [$e next_p]} {
		if {$i == $index} {return $e}
		incr i
	}
	return {}; # not found
}
 
LinkedList::Container instproc list {} {
	set es {}
	for {set e [my first]} {$e != {}} {set e [$e next_p]} {
		lappend es $e
	}
	return $es
}
 
LinkedList::Container instproc rlist {} {
	set es {}
	for {set e [my last]} {$e != {}} {set e [$e prev_p]} {
		lappend es $e
	}
	return $es
}
 
LinkedList::Container instproc index {element} {
	set i 0
	for {set e [my first]} {$e != {}} {set e [$e next_p]} {
		if {$e == $element} {return $i}
		incr i
	}
	return -1; # not found
}
 
LinkedList::Container instproc pop {} {
	set last [my last]
	my drop $last
}
 
LinkedList::Container instproc push {element} {
	set last [my last]
	if {$last == {}} {
		my add $element
	} else {
		$last append $element
	}
	return $element
}
 
LinkedList::Container instproc shift {} {
	my drop [my first]
}
 
LinkedList::Container instproc unshift {element} {
	set first [my first]
	if {$first == {}} {
		my add $element
	} else {
		$first prepend $element
	}
	return $element
}
 
LinkedList::Container instproc drop {element} {
	if {[my unlink $element] >= 0} {
		return $element
	}
}
 
LinkedList::Container instproc delete {element} {
	if {![Object isobject $element]} {
		$element destroy
	}
}

# InstMixin this class into element class to use container
Class LinkedList::Element -parameter {
	{prev_p {}}
	{next_p {}}
	{container {}}
}
 
LinkedList::Element instproc destroy {} {
	my drop
	next
	return [self]
}
 
LinkedList::Element instproc drop {} {
	if {[my container] != {}} {
		[my container] drop [self]
	}
}
 
LinkedList::Element instproc index {} {
	if {[my container] != {}} {
		[my container] index [self]
	}
}
 
LinkedList::Element instproc append {element} {
	if {[my container] != {}} {
		$element prev_p [self]
		$element next_p [my next_p]
		if {[my next_p] != {}} {
			[my next_p] prev_p $element
		}
		my next_p $element
		[my container] add $element
	}
}
 
LinkedList::Element instproc prepend {element} {
	if {[my container] != {}} {
		$element prev_p [my prev_p]
		$element next_p [self]
		if {[my prev_p] != {}} {
			[my prev_p] next_p $element
		}
		my prev_p $element
		[my container] add $element
	}
}
 
LinkedList::Element instproc status {} {
	append stat "  container = [my container]\n"
	append stat "  index     = [my index]\n"
	append stat "  element   = [self]\n"
	append stat "  neighbors = ([my prev_p]) => ([self]) => ([my next_p])\n"
}
 
package provide linkedlist 3.0

パッケージにしておきました。
linkedlist.3.0.120823

過去にやった「リレーショナルデータベースの間違った使い方」

いきあたりばったりのアーキテクチャと教訓:リレーショナルデータベースの間違った使い方10項目

動的なテーブルの作成 ⇒ INSERTのトリガで月ごとのテーブル作るようにしたらINSERTが遅すぎて失敗しました。
テーブルをキャッシュとして使う ⇒ Railsではセッションキャッシュに使ってたけど、のちにcookieセッションに変わったと思う。
テーブルをキューとして使う ⇒ やりました。Dequeを使うのがいいのですが、自作のデータグリッドウィジェットと自作のTcl版ActiveRecordの依存性が高すぎて変更が大変。いまだに変えられない。
テーブルをログとして使う ⇒ 他にどうすんの?KVSってやつ?検索とか似たような機能あるの?
分散したグローバルなロック ⇒ なにそれ?
ストアドプロシージャ ⇒ 使ったことない。
使われない項目 ⇒ いくつかあるけど、そんなに多くない。検索キーにならないカラムが増えていくのを、YAML形式とかでテキストカラムに押し込めるべきか悩んでる。
ORMによって繰り返されるクエリ ⇒ よくやりました。暗黙的遅延ロード機能なんかつけるからだ。ActiveRecordなら:include=>"groups"、EFなら.Include("groups")を使う。
負荷のコントロール ⇒ DBじゃないけど、ウェブサーバのリバースプロキシとかはよくやりました。メモリリークだらけのRailsには必要だった。

break可能なコードブロック

Tclにはgotoみたいに直列に書いたコードをスキップする制御構文がない。

例えばなんかデータを受信して、チェックしたり加工したりしたあとでどこかに記録するけど、
結果によらず受信回数はカウントしときたい場合、こんなコードを書いたとする。

proc on_receive {data} {
  global cnt
  set res [validate $data]
  if {$res == 1} {
    incr cnt; return $res
  }
  set res [convert $data data2]
  if {$res == 2} {
    incr cnt; return $res
  }
  set res [save $data2]
  incr cnt; return $res
}

incr cnt; return $res; というコードを何度も書かないといけないのが嫌だ。

そこで今まではこんな書き方をしてたことがあった。

proc on_receive {data} {
  global cnt
  while {1} {
    set res [validate $data]
    if {$res == 1} break
    set res [convert $data data2]
    if {$res == 2} break
    set res [save $data2]
    break; # Don't forget!
  }
  incr cnt; return $res
}

ときどきだけど、while {1} の最後のbreakを忘れて、
無限ループに陥ることがあったので、ケアレスミスを減らすため、こう変えた。

proc on_receive {data} {
  global cnt
  foreach once {1} {
    set res [validate $data]
    if {$res == 1} break
    set res [convert $data data2]
    if {$res == 2} break
    set res [save $data2]
  }
  incr cnt; return $res
}

ちなみにforeachのところは、こうしても同じ。

proc on_receive {data} {
  global cnt
  for {} {1} break {
    # ...
  }

だけど何とも見た目が悪い。
初めて見た人は foreach {var1 var2} {1 2} break 並みに分かりづらいだろう。
多重代入のためにlassignが導入されたように、やはり目的が分かる名前の制御構文があったほうがかっこいい。

8.6のtry(8.5ではtcllibに入ってる)を使って、blockという途中でbreak可能な制御構文を作ってみる。

if {[info tclversion] <= 8.5} {
package require try
}
proc block {script} {
  try {
    return [uplevel 1 $script]
  } on break {result options} {}
}

さっきのコードを書き換えてみる。

proc on_receive {data} {
  global cnt
  block {
    set res [validate $data]
    if {$res == 1} break
    set res [convert $data data2]
    if {$res == 2} break
    set res [save $data2]
  }
  incr cnt
  return $res
}

どうかな。

これだけでblockなんて名乗るのはおこがましいので、もうちょい慎ましい名前を考えたら使ってみようと思う。

候補

  • breakable
  • through

Tclだとそんなところに脳のリソースを割かないといけないの?とか言わないでね。

ソースコード中のUnicodeリテラルを展開して出力

Tclスクリプトにマルチバイト文字が入ってると、環境によっては文字化けして実行できないという問題がありました。
コメントは英語表記にしてあるのでいいとして、ギリシャ文字のシグマとか、パスワード隠し記号とかは見た目上どうしても使いたいので、そういう記号は全部Unicodeリテラル表記にしました。ここのツールを使うと簡単にUnicodeリテラルを調べられます。

http://www.snible.org/java2/uni2java.html

ついでに、あとでリテラルが何の記号に対応するかみたいという場合もあると思って、以下のようなスクリプトを書きました。
これでリテラルを展開した結果を見られます。

# filename: subst_unicode_literal.tcl
proc read_file {f} {
	set ch [open $f r]
	set out [read $ch]
	close $ch
	return $out
}
 
proc extract_unicode_literals {str _ll _lu} {
	upvar $_ll ll
	upvar $_lu lu
	set ll [lsort -unique [regexp -all -inline -expanded {(\\u[0-9A-Fa-f]{4})} $str]]
	set lu [subst [join $ll]]
}
 
proc make_map {ll lu} {
	set map {}
	foreach l $ll u $lu {
		lappend map $l $u
	}
	return $map
}
 
set f [lindex $argv 0]
set str [read_file $f]
 
set ll {}
set lu {}
extract_unicode_literals $str ll lu
set map [make_map $ll $lu]
set out [string map $map $str]
 
puts -nonewline $out

仕組みとしては単に\uXXXXという文字を見つけて、置換してるだけです。
Tclの場合は、Unicodeは16bitまでしか定義されてないので、リテラル表記は例外なくこれで処理できるようです。
なお、{\uXXXX}という表記はリテラルとはみなされないのですが、ここでは考慮してません。

Jet/ACEでExcelファイルをCSVに変換する

ある分析装置がxlsx形式でレポート出力するのでExcelのCOMインターフェース経由でCSVに落としてから処理していました。
この方法だとMicrosoft Excelが必須になってしまいます。
あとExcel本体経由なので、セキュリティ設定によっては手動でロック解除しないと開けないとかいう場合もあります。
そこでライセンス料をケチるべく、Excelなしでxls/xlsx -> CSV変換プログラムを書く方法を調べました。

// filename: Excel2CSV.cs
using System;
using System.Data;
using System.Data.OleDb;
using System.IO;
 
namespace Excel2CSV
{
    public class Excel2CSV
    {
        const int SUCCESS = 0;
        const int MISSING_FILE_ERROR = 1;
        const int FILE_EXTENSION_ERROR = 2;
        public static int Main(string[] args)
        {
            string path = args[0];
            var finfo = new FileInfo(path);
            if (!finfo.Exists)
            {
                return MISSING_FILE_ERROR;
            }
            string connectionString;
            switch (finfo.Extension.ToLower())
            {
                case ".xls":
                    connectionString = String.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties=\"Excel 8.0;HDR=NO;IMEX=1;TypeGuessRows=0;\"", path);
                    break;
                case ".xlsx":
                    connectionString = String.Format("Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties=\"Excel 12.0 Xml;HDR=NO;IMEX=1;\"", path);
                    break;
                default:
                    return FILE_EXTENSION_ERROR;
            }
 
            OleDbConnection oleConn = new OleDbConnection(connectionString);
            OleDbCommand oleCmd = new OleDbCommand();
            OleDbDataReader oleReader;
 
            oleConn.Open();
            oleCmd.Connection = oleConn;
 
            DataTable tables = oleConn.GetSchema("Tables");
            foreach (DataRow row in tables.Rows)
            {
                string sheetName = row["TABLE_NAME"].ToString();
                string sheetText = "";
                try
                {
                    sheetText += String.Format("[{0}]", sheetName) + Environment.NewLine;
                    oleCmd.CommandText = "SELECT * FROM [" + sheetName + "]";
                    oleReader = oleCmd.ExecuteReader();
                    while (oleReader.Read())
                    {
                        int fieldCount = oleReader.FieldCount;
                        string[] line = new string[fieldCount];
                        for (int i = 0; i < fieldCount; i++)
                        {
                            string val = oleReader[i].ToString();
                            double dval;
                            if (double.TryParse(val, out dval))
                            {
                                line[i] = dval.ToString(); // 12,345.67みたいな数字を12345.67に変換。
                            }
                            else
                            {
                                line[i] = val;
                            }
                        }
                        sheetText += String.Join(",", line) + Environment.NewLine;
                    }
                    oleReader.Close();
                    Console.WriteLine(sheetText);
                }
                catch
                {
                }
            }
            oleConn.Close();
            oleCmd.Dispose();
            oleConn.Dispose();
            return SUCCESS;
        }
    }
}

コンパイルは、

csc Excel2CSV.cs

ファイル名を引数に与えると、標準出力にCSVを出力します。エラーは終了コードで通知します。

Excel2CSV.exe sample.xlsx > sample.xlsx.csv
echo %ERRORLEVEL%
0

必須コンポーネント

Microsoft Access Database Engine 2010 Redistributable

.NET Framework 2.0 以上?

.NET Framework 4.0向けに コンパイルしたもの Excel2CSV

ただ、COM経由で取得したCSVとは、数値の値が違うことがあります。
原因はDAOを使った場合は、表示書式を適用した後の値しか取得できないためのようです。
たとえば、12345.6789という数値を持つセルに、"小数以下桁数2桁、桁区切り"という書式が設定されている場合、12,345.68が抽出されます。
考え方によってはこの方がいいというケースも、だめなケースもあるかも知れません。
とりあえず、桁区切り(,)の書式はCSVにとって邪魔なので、上記のプログラムでは数値とみなせる文字列は一旦数値に変換しています。

ちなみにxdoc2txtの場合は、桁区切りなし、四捨五入済みの数値を吐きます。
というか、いろいろ実験してたらxdoc2txtの出力ってゴミが入ってたりしてプログラムから後処理するのがめんどくさげ。
そもそも商用ライセンスは1000本単位じゃないと買えないので却下。

あと、xlsxをxlsに変換してExcel2CSVにかけると、可視シートだけが出力されるという微妙な違いもありました。
普通は隠しシートの内容を見たいということはないと思いますが。

XOTcl というか Next Scripting Framework 2.0b3のコンパイル

XOTclの最新安定版はteacupで配布してる1.6.7ですが、今のところTcl8.6b2で使うことができません。
私の場合、XOTclに依存しまくってるので、8.4から8.6に1つ飛ばしで移行しようと思ったところ、これだけが引っかかっていて、
仕方なく8.5に移行しました。

一方XOTcl開発チームの人々はNext Scripting Framework (NSF)という壮大なプロジェクトを立ち上げ、XOTclの次期バージョンである2.0はそのフレームワークの上で実装されているそうです。すでにNSF Beta2のソースコードが配布されているのでコンパイルしてみました。

MinGWで比較的簡単にコンパイルできました。
Tcl関連のソースコードでVC6で簡単にコンパイルできるものってあんまりないです。

必要なもの
C:/src/tcl8.6b2 --- Tclのソースコード。tcl86b2-src.zip
C:/src/nsf2.0b3 --- NSFのソースコード。nsf2.0b3.tar.gz

MinGW shellを起動し、

cd /c/src/nsf2.0b3
./configure --prefix=/c/bin/tcl8. 6b2/lib --exec-prefix=/c/tcl8.6b2 --enable-threads
make
make install

簡単なスクリプトは動くようです。

(bin) 1 % package require XOTcl
2.0b3
(bin) 2 % namespace import xotcl::*
(bin) 3 % Class Dog
::Dog
(bin) 4 % Dog instproc init {} {puts "created"}
::nsf::classes::Dog::init
(bin) 5 % Dog instproc hello {} {puts "Bow"}
::nsf::classes::Dog::hello
(bin) 6 % set dog [Dog new]
created
::nsf::__#0
(bin) 7 % $dog hello
Bow
(bin) 8 % info patchlevel
8.6b

が、いろんな機能を使ってる複雑なコードの場合はエラーが出ました。以下例。
Warning: Arguments 'component xdobry::sqlite' to constructor of object ::idemeta are most likely not processed
no current object; command called outside the context of a Next Scripting method

XOTcl 1と2の非互換性についてはドキュメントがありますが、
Incompatibilities between XOTcl 1 and XOTcl 2

これで全部網羅してるとは思えないし、
2で追加されたというcurrentというコマンドは存在しないと言われます。

せっかく作ったので丸ごと置いときます。
Tcl/Tk8.6b2 with nsf2.0b3