練炭ブログ

X680x0、Irvine、DMonkey、Proxomitron などの情報を扱ってます。

Irvine: スクリプトのショートカットキー設定

コメントなし»

ツールメニューに表示される種類のスクリプトでは、action.shortcut にショートカットキーを代入すると、そのキーでスクリプトを直接実行できるようになります。

以下の4つのスクリプトが該当します。

  • OnMainMenuClick
  • OnListMenuClick
  • OnTreeMenuClick
  • OnTrayMenuClick

ショートカットキーは修飾キー + 仮想キーコード(参考: 仮想キーの状態)で指定します。

  • Shift …… 0x2000
  • Ctrl …… 0x4000
  • Alt …… 0x8000

OnMainMenuClick() などのクリックイベント内で代入すると、クリックしてスクリプトを実行するまでショートカットキーが設定されません。またオプション設定でスクリプトを更新するとショートカットキーの設定が消えます。

メニューの更新イベント OnMenuUpdate() 内で代入すれば、実行しなくてもメニューを表示するだけでショートカットキーが設定されます。ただしメニューを表示するまでは設定されませんし、オプション設定でスクリプトを更新すると消えるのは同じです。

この記事を書いたきっかけ: Irvine Part29

[6月10日追記] 例えば Shift+D に割り当てると、キューフォルダを右クリックして「名前の変更」実行中に大文字の D を入力しようとしてもショートカットキーとして反応してしまいます。

DMonkey: String/RegExp.replace で0回のマッチをキャプチャすると number 0 になる

コメントなし»

[4月29日追記] 訂正: number 0 になるのではなく、キャプチャ結果が引数として渡されないため、マッチした位置(index)を参照していただけでした(0回のマッチと1回以上のマッチの場合で arguments.length が違う)。申し訳ありません。

  • String.replace() や RegExp.replace で、
  • 正規表現に (~)?(~)* を使い、
  • 置換文字列として関数を指定していると、
  • 最後のキャプチャ部分が0回の繰り返しにマッチした時、
  • キャプチャ結果として関数の引数に number 型の 0 が渡される。キャプチャ結果が関数の引数に渡されない。

通常は空文字列が渡されるのですが、上記のように最後のキャプチャが0回マッチだと 0 が渡されますキャプチャ結果が渡されません。他の文字列と + 演算子で繋げたりすると '0' という文字列として扱われてしまうので注意が必要です。

alert (
  'ab'.replace (/a(\d)?b/, function (s, p1) { return typeof p1; })
);
//number

alert (
  'ab'.replace (/a(\d)*b/, function (s, p1) { return typeof p1; })
);
//number

なお、各種ブラウザでは0回マッチでもキャプチャ結果が引数として渡されますが、その内容は Firefox 12 では空文字列(string)、Internet Explorer 8 と Google Chrome 18 では undefined になります。

常に文字列で渡される Firefox 形式が楽だと思うのですが、ECMAScript 的にはどの動作が正しいんでしょうか?

対処法

  • 正規表現の末尾に () を付ける。
  • (~)?((~)?) のように外側をキャプチャする。
  • 置換文字列として指定された関数内で、number 型か調べて空文字列に置き換える。

一つ目の方法の例。

alert(
  'ab'.replace(/a(\d)?b()/, function (s, p1) { return typeof p1; })
);
//string

alert(
  'ab'.replace(/a(\d)*b()/, function (s, p1) { return typeof p1; })
);
//string

DMonkey: URL オブジェクトの各プロパティ内容

コメントなし»

URL オブジェクトの各プロパティが実際にどのような内容になるか調べてみました。

空文字列以外はクオートを省略してあります。

var u = new URL ('http://example.com:8080/dir/index.html?q=1#frag');
alert ([
  u.url,       // http://example.com:8080/dir/index.html?q=1#frag
  u.protocol,  // http
  u.host,      // example.com:8080
  u.hostname,  // example.com
  u.port,      // 8080
  u.path,      // /dir/index.html?q=1#frag
  u.directory, // /dir/
  u.filename,  // index.html
  u.query      // ?q=1#frag
].join ("\n"));

最小限の構成要素だと以下のようになります。

var u = new URL ('https://example.com/');
alert ([
  u.url,       // https://example.com/
  u.protocol,  // https
  u.host,      // example.com
  u.hostname,  // example.com
  u.port,      // ''
  u.path,      // /
  u.directory, // ''
  u.filename,  // ''
  u.query      // ''
].join ("\n"));

DMonkey: URL.expand()

コメントなし»

URL オブジェクトの expand() メソッドの挙動を調べてみました。

var u = 'http://example.com/dir/file?foo=123';

alert ((new URL (u)).expand ('abc'));
// http://example.com/dir/abc

alert ((new URL (u)).expand ('abc;def'));
// http://example.com/dir/abc

alert ((new URL (u)).expand ('?bar=456'));
// http://example.com/dir/?bar=456

alert ((new URL (u)).expand ('/'));
// http://example.com/

alert ((new URL (u)).expand ('./'));
// http://example.com/dir/

alert ((new URL (u)).expand ('../'));
// http://example.com/

alert ((new URL (u)).expand ('http://example.jp/'));
// http://example.jp/

alert ((new URL (u)).expand ('//example.jp/'));
// http://example.com//example.jp/

  • 引数の ; 以降が削除される。
  • 引数が ? からはじまると(query のみ)、filename が空として解釈される。
  • 引数が // からはじまると(net_path)、絶対パス(abs_path)として解釈される。

HTML ページ内の相対 URL を絶対 URL に整形するのに使えればと思っていたのですが、自前で書くしかないようです。

DMonkey: Global.parseInt()

コメントなし»

dmonkeydoc.html より。

parseInt(値) ... 値を整数値に変換します。返値は整数値。

参考:parseInt - MDN

JavaScript では値はまず文字列化してから解析されますが、DMonkey では文字列化されず型ごとに変換アルゴリズムが適用されます。

基数を指定する第二引数は渡せません。

parseInt (整数) → そのまま

parseInt (小数) → 小数部を四捨五入 (round)
parseInt (-1.5) → -2
# JavaScript では parseInt (-1.5) → -1

parseInt (true) → 1
parseInt (false) → 0

parseInt () → undefined

parseInt (NaN) → NaN
parseInt (null) → NaN

var Infinity = Number.POSITIVE_INFINITY;
var undefined = (function () {}) ();

parseInt (Infinity) → NaN
parseInt (undefined) → NaN

文字列は、DMonkey の内部で Delphi の StrToIntDef 関数を呼び出しています(デフォルト値 0)。

/^ *\d+$/ → 10進数(0 または正数)
/^ *+\d+$/ → 10進数(0 または正数)
/^ *-\d+$/ → 10進数(0 または負数)

/^ *[-+]?(\d+\.?|\d*\.\d+) *$/ → 0

/^ *\d+(\.\d*)?e\d* *$/i → 0

/^ *$[0-9a-f]+$/i → 16進数
/^ *0x[0-9a-f]+$/i → 16進数

それ以外 → NaN

ややこしい動作をするので正規表現にしてみましたが、適当に入れてみたパターンから逆算しただけなので間違いがあるかも知れません。

Irvine/DMonkey: 関数クロージャよりクラスのメソッドが優先される場合がある

コメントなし»
class Test {
  function a () { alert ('class method'); }

  function test () {
    function a () { alert ('local func'); }
    function b () { a (); }

    a ();   // local func
    b ();   // class method
  }
}

(new Test ()).test ();

これはハマる。

Dorothy2 用のスクリプトを作る時は、呼び出し元のクラスで定義されている関数名と重複しないようにしなければなりません(変数名も)。

特に設定プログラム(Dorothy2\setting\*.set)は、Dorothy2set.dms で定義されている class SettingForm に多数の関数が含まれているので注意が必要です。

DMonkey: 名前付き関数式で定義されたグローバル関数からはクロージャが使えない

コメントなし»

名前付き関数式で定義されたグローバル関数からはクロージャが使えません。

(function () {
  var a = 'abc';
  void function Foo () { alert (a); };
}) ();

Foo ();   // Exception: ENameError(3) => undefined -a

グローバルスコープだけを見ているようです。

var a = 'outside';

var f = (function () {
  var a = 'inside';
  return function Foo () { alert (a); };
}) ();

f ();   // inside

Foo ();   // outside

(function () {
  var a = 'inside2';
  Foo ();   // outside
})();

定義した関数の中身に関しては通常通りクロージャが働きます。

(function () {
  void function Foo () {
    var a = 'abc';
    (function () { alert (a); }) ();
  };
}) ();

Foo ();   // abc

DMonkey: 名前付き関数式はグローバルスコープに対する関数宣言として動作する

コメントなし»

ECMAScript で名前付き関数式(Named Function Expression: NFE)を使うと、その関数の内部でのみ関数名を参照できます。

一方、DMonkey における NFE はグローバルスコープに対する関数宣言として動作します(同じ関数どころか、どこからでも参照できてしまいます)。

function foo () {
  var a = function bar () { alert ('baz'); };
}

foo ();
bar ();   // baz

トップレベルで普通に関数や変数宣言をした場合と同じ扱いになるようです。Global オブジェクトに入るわけではないので、alert (Global.hasKey ('bar')); は false です。

これはつまり関数内で NFE を使ってしまうと外部にまで晒されるということですが、逆に、あえて NFE を使うことで関数内からグローバル関数を定義することが出来る、とも言えます。

以下のように prototype への読み書きも出来るので、オブジェクトを定義することも出来ます。

(function () {
  void function Foo () { };
  Foo.prototype.bar = function () { alert ('baz'); };
}) ();

var x = new Foo ();
x.bar ();   // baz

最初のサンプルコードでは代入式という形で関数式を記述しましたが、今度は頭に void、末尾に ; を着ける方法をとっています。他に、true && function Foo () { }; といったパターンでも動作するので、関数式であればなんでもOKっぽいです。

なお、関数内からグローバルスコープでオブジェクトを定義したい場合、普通に考えると以下のようなコードになると思いますが、DMoneky ではエラーで動作しません。

(function () {
  Foo = function () { };
  Foo.prototype.bar = function () { alert ('baz'); };
}) ();
var x = new Foo ();
x.bar ();   // Exception: ENameError() => bar

(function () {
  function x () { }
  x.prototype.bar = function () { alert ('baz'); };
  Foo2 = x;
}) ();
var x2 = new Foo2 ();
x2.bar ();   // Exception: ENameError() => bar

Irvine の古い履歴フォルダを消す方法(正)

コメントなし»

Irvine の古い履歴フォルダを消す方法(誤)

オプション画面よく見たらあった。
っていうか全然見てなかった。

DMonkey: String オブジェクトの text プロパティ

コメントなし»
var s = new String ('abc');
alert (typeof s.text);   // 'string'

String.text は string 型。

var s = new String ('abc');
s.text.text = 'xyz';   // Exception: ENameError() => member assign error text
alert (s);

なので、String.text.text は(string.text ということなので)書き換え不可。

string がプリミティブ型で、String がオブジェクト型、という区別の筈です。