スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

カテゴリ : スポンサー広告

System.getTickCountのオーバーフロー

吉里吉里のSystem.getTickCountはオーバーフローするのかなあ、と思って調べてみたメモ。
結果としてはしないっぽい。

元になるtickCount自体はtimeGetTimeの32bitで得ているようです。これ自体は49日程度しか持ちません。
それがオーバーフローするとTVPTickCountBiasに記録され、System.getTickCountで得られる値には64bitになっています。
ということで吉里吉里を100年つけっぱなしでも特に問題ないようです。

続きを閉じる▲

スポンサーサイト

タグ : 吉里吉里 TJS

カテゴリ : TJS

tjsで名前空間を使うメモ

Scripts.execStorageのコンテキスト指定によって名前空間らしきものが使えます。
コンテキスト指定ができるのは開発版のみです
参考:吉里吉里安定版と開発版の違いメモ

以下サンプル。startup.tjs, a.tjs, b.tjsの3つにわかれています。
// startup.tjs
Plugins.link("ScriptsEx.dll"); // Scripts.getObjectContextを使う
class Namespace {} // ネームスペースとして使うクラス

Scripts.execStorage("a.tjs",,Namespace);
Scripts.execStorage("b.tjs",,Namespace);

Debug.message("new Namespace.A()");
var a = new Namespace.A();

Debug.message("new Namespace.B()");
var b = new Namespace.B();

Debug.message('a instanceof "Namespace.A" == ' + (a instanceof "Namespace.A"));
Debug.message('a instanceof "A" == ' + (a instanceof "A"));
Debug.message('Scripts.getClassNames(a).join(", ") == ' + Scripts.getClassNames(a).join(", "));
Debug.message('Scripts.getClassNames(b).join(", ") == ' + Scripts.getClassNames(b).join(", "));

// a.tjs
if (Scripts.getObjectContext(this) != Namespace) { throw new Exception(); }
class A {
  function A() { Debug.message("A()"); }
}

// b.tjs
if (Scripts.getObjectContext(this) != Namespace) { throw new Exception(); }
class B extends Namespace.A {
  function B() { super.A(); Debug.message("B()"); }
}

コンテキストにNamespaceを指定しているので、AとBはNamespaceの中で定義されます。

a.tjsとb.tjsではScripts.getObjectContextを使ってNamespaceコンテキストで呼ばれているかチェックしています。
ミスがないようにチェックしているだけなので無くても動きます。

あとはNamespace.AやNamespace.Bで使えます。
ただしinstanceofでは"Namespace.A"ではなく"A"となります。
少し面倒なのでinstanceofの代わりに適当な関数を作ってもいいかもしれません。

このスクリプトを実行すると、Debug.messageの出力は以下のようになります。
new Namespace.A()
A()
new Namespace.B()
A()
B()
a instanceof "Namespace.A" == 0
a instanceof "A" == 1
Scripts.getClassNames(a).join(", ") == A
Scripts.getClassNames(b).join(", ") == B, A

実際はこんなことをせずにNamespace_Aのようなクラス名にしてしまうのがいいと思います。

続きを閉じる▲

タグ : 吉里吉里 TJS

カテゴリ : TJS

tjsの配列でC#のLINQが使う

使いたかったのでArrayに関数増やしてみた。
ひとつひとつは大したこと無くても数が多くて面倒だった・・・・・・。

実際にtjsで使いどころがあるかは微妙。吉里吉里でツール作るときとかはいいかも。
使いそうなのだけコピペして使ってもいいんじゃないかしら。


/// 指定された位置の要素を返すが、添え字が範囲外の場合は例外を投げる
/// @param index 要素の位置
/// @return 指定された位置の要素
Array.elementAt = function(index) {
  if (index < 0 || index >= this.count) {
    throw new Exception("配列のindexが範囲外です");
  }
  return this[index];
};

/// 配列をコピーして返す
/// @return 配列のコピー
Array.clone = function() {
  var tmp = [];
  tmp.assign(this);
  return tmp;
};

/// 配列の各要素の関数を適用する
/// @param func 各要素を受け取って処理する関数
Array.foreach = function(func) {
  for (var i = 0; i < this.count; ++i) {
    func(this[i], i);
  }
};

/// 配列から辞書配列を生成する
/// キーが重複すると例外を投げる
/// @param keySelector 各要素を受け取ってキーを返す関数
/// @param elementSelector 各要素を受け取って辞書配列の値を返す関数
/// @return keySelectorの戻り値をキー、たいおうするelementSelectorの戻り値を値とする辞書配列
Array.toDictionary = function(keySelector, elementSelector) {
  if (elementSelector === void) elementSelector = function(elm) { return elm; };
  var tmp = %[];
  for (var i = 0; i < this.count; ++i) {
    var key = keySelector(this[i], i);
    if (tmp[key] !== voidthrow new Exception("キーが重複しています。");
    tmp[key] = elementSelector(this[i]);
  }
  return tmp;
};

/// 配列から辞書配列を生成する
/// 辞書配列の要素が配列のため、キーが重なっても格納できる
/// @param keySelector 各要素を受け取ってキーを返す関数
/// @param elementSelector 各要素を受け取って辞書配列の値の配列に入れる要素を返す関数
/// @return keySelectorの戻り値をキー、対応するelementSelectorの戻り値が入った配列を値とする辞書配列
Array.toLookup = function(keySelector, elementSelector) {
  if (elementSelector === void) elementSelector = function(elm) { return elm; };
  var tmp = %[];
  for (var i = 0; i < this.count; ++i) {
    var key = keySelector(this[i], i);
    if (tmp[key] === void) tmp[key] = [];
    tmp[key].add(elementSelector(this[i]));
  }
  return tmp;
};

/// 配列の各要素に変換関数を適用する
/// @param selector 各要素を受け取って、変換後の要素を返す関数
/// @return selectorの戻り値が入った配列
Array.select = function(selector) {
  var tmp = [];
  for (var i = 0; i < this.count; ++i) {
    tmp.push(selector(this[i], i));
  }
  return tmp;
};

/// 配列の各要素に変換関数を適用する
/// 要素1つが1つ以上の要素に変換される
/// @param selector 各要素を受け取って、変換後の要素の配列を返す関数
/// @return selectorの返した配列を展開して格納した配列
Array.selectMany = function(selector) {
  var tmp = [];
  for (var i = 0; i < this.count; ++i) {
    tmp.push(selector(this[i], i)*);
  }
  return tmp;
};

/// 条件を満たす要素のみを抽出する
/// @param predicate 各要素を受け取って条件を満たすか返す関数
/// @return 条件を満たす要素のみを抽出した配列
Array.where = function(predicate) {
  var tmp = [];
  for (var i = 0; i < this.count; ++i) {
    if (predicate(this[i], i)) {
      tmp.push(this[i]);
    }
  }
  return tmp;
};

/// 配列の全要素を条件に従ってグループ分けする
/// @param keySelector 各要素からキーを返す関数
/// @return keySelectorの戻り値がキー、対応する各要素が入った配列が値となった辞書配列
Array.groupBy = function(keySelector) {
  var tmp = %[];
  for (var i = 0; i < this.count; ++i) {
    var key = keySelector(this[i], i);
    if (tmp[key] === void) tmp[key] = [];
    tmp[key].push(this[i]);
  }
  return tmp;
};

/// 配列を条件に従って昇順に整列する
/// @param keySelector 各要素から比較に使う値を返す関数
/// @param comparer 要素を2つ受け取って、最初の値が小さければtrueとなる関数
/// @return 整列ずみの配列
Array.orderBy = function(keySelector, comparer) {
  if (comparer === void) {
    comparer = function(first, second) { return first < second; };
  }
  var tmp = [];
  for (var i = 0; i < this.count; ++i) {
    var key = keySelector(this[i], i);
    var inserted = false;
    for (var j = 0; j < tmp.count; ++j) {
      if (comparer(key, tmp[j])) {
        tmp.insert(j, this[i]);
        inserted = true;
        break;
      }
    }
    if (!inserted) tmp.push(this[i]);
  }
  return tmp;
};

/// 配列を条件に従って降順に整列する
/// @param keySelector 各要素から比較に使う値を返す関数
/// @param comparer 要素を2つ受け取って、最初の値が小さければtrueとなる関数
/// @return 整列ずみの配列
Array.orderByDescending = function(keySelector, comparer) {
  if (comparer === void) {
    comparer = function(first, second) { return first > second; };
  } else {
    comparer = function { return !comparer(...); } incontextof %[ comparer:comparer ];
  }
  return orderBy(keySelector, comparer);
};

/// 自身(outer)と指定された配列(inner)を内部結合します
/// @param outer 結合する配列
/// @param outerKeySelector 配列outerの各要素を受け取ってキーを返す関数
/// @param innerKeySelector 配列innerの各要素を受け取ってキーを返す関数
/// @param resultSelector キーが一致する配列(outer)と配列(inner)の要素を受け取って結果となる要素を返す関数
/// @return resultSelectorの戻り値が入った配列
/// Array.joinは使われているのでArray.innerJoin
Array.innerJoin = function(inner, outerKeySelector, innerKeySelector, resultSelector) {
  var outerKeys = %[];
  for (var i = 0; i < this.count; ++i) {
    var key = outerKeySelector(this[i]);
    if (outerKeys[key] === void) outerKeys[key] = [];
    outerKeys[key].add(this[i]);
  }
  
  var results = [];
  for (var i = 0; i < inner.count; ++i) {
    var key = innerKeySelector(inner[i]);
    if (outerKeys[key] === voidcontinue;
    for (var j = 0; j < outerKeys[key].count; ++j) {
      results.add(resultSelector(outerKeys[key][j], inner[i]));
    }
  }
  return results;
};

/// 自身(outer)と指定された配列(inner)を左外部結合します
/// @param outer 結合する配列
/// @param outerKeySelector 配列outerの各要素を受け取ってキーを返す関数
/// @param innerKeySelector 配列innerの各要素を受け取ってキーを返す関数
/// @param resultSelector キーが一致する配列(outer)要素と配列(inner)要素の配列を受け取って結果となる要素を返す関数
Array.groupJoin = function(inner, outerKeySelector, innerKeySelector, resultSelector) {
  var innerKeys = %[];
  for (var i = 0; i < inner.count; ++i) {
    var key = innerKeySelector(inner[i]);
    if (innerKeys[key] === void) innerKeys[key] = [];
    innerKeys[key].add(inner[i]);
  }
  
  var results = [];
  for (var i = 0; i < this.count; ++i) {
    var key = outerKeySelector(this[i]);
    results.add(resultSelector(this[i], innerKeys[key] !== void ? innerKeys[key] : []));
  }
  return results;
};

/// 配列の先頭から指定された数だけ抽出する
/// @param count 取り出す数
/// @return 配列の先頭から指定された数だけ取り出した配列
Array.take = function(count) {
  if (count > this.count) count = this.count;
  var tmp = [];
  for (var i = count-1; i >= 0; --i) {
    tmp[i] = this[i];
  }
  return tmp;
};

/// 配列の先頭から指定された数だけ飛ばして、残りを抽出する
/// @param count 飛ばす数
/// @return 配列の先頭から指定された数だけ飛ばした残りの配列
Array.skip = function(count) {
  var tmp = [];
  for (var i = count; i < this.count; ++i) {
    tmp.add(this[i]);
  }
  return tmp;
};

/// 配列の先頭から条件を満たす限りの要素を抽出する
/// @param predicate 各要素を受け取って条件を満たすか返す関数
/// @return 配列の先頭から条件を満たす限りまでの配列
Array.takeWhile = function(predicate) {
  var count = 0;
  while (predicate(this[count], count) && ++count < this.count);
  return this.take(count);
};

/// 配列の先頭から条件を満たす限りの要素を飛ばし、残りを抽出する
/// @param predicate 各要素を受け取って条件を満たすか返す関数
/// @return 配列の先頭から条件を満たす限り飛ばした残りの配列
Array.skipWhile = function(predicate) {
  var count = 0;
  while (predicate(this[count], count) && ++count < this.count);
  return this.skip(count);
};

/// 配列から同一の要素を取り除く
/// @return 重複した要素を取り除いた配列
Array.distinct = function() {
  var tmp = [];
  var values = %[];
  for (var i = 0; i < this.count; ++i) {
    var value = this[i];
    if (values[value]) continue;
    tmp.add(value);
    values[value] = true;
  }
  return tmp;
};

/// 現在の配列の後ろに、指定された配列を連結する
/// @param array 連結する配列
/// @return 連結された配列
Array.concat = function(array) {
  var tmp = [];
  tmp.push(this*, array*);
  return tmp;
};

/// 配列を添え字が同じ各要素ごとにマージする
/// 配列の長さが異なる場合、短い方の長さになる
/// @param array マージする配列
/// @param resultSelector 2つの要素を受け取って、マージした要素を返す関数
/// @return resultSelectorの戻り値が入った配列
Array.zip = function(array, resultSelector) {
  var tmp = [];
  var count = this.count > array.count ? array.count : this.count;
  for (var i = count-1; i >= 0; --i) {
    tmp[i] = resultSelector(this[i], array[i]);
  }
  return tmp;
};

/// 2つの配列の和集合を生成する
/// @param array 対象となる配列
/// @return 指定された配列との和集合の配列
Array.union = function(array) {
  var tmp = [];
  var values = %[];
  for (var i = 0; i < this.count; ++i) {
    var value = this[i];
    if (values[value]) continue;
    tmp.add(value);
    values[value] = true;
  }
  for (var i = 0; i < array.count; ++i) {
    var value = array[i];
    if (values[value]) continue;
    tmp.add(value);
    values[value] = true;
  }
  return tmp;
};

/// 2つの配列から積集合を生成する
/// @param array 対象となる配列
/// @return 指定された配列との積集合の配列
Array.intersect = function(array) {
  var values = %[];
  for (var i = this.count-1; i >= 0; --i) {
    values[this[i]] = true;
  }
  var tmp = [];
  for (var i = 0; i < array.count; ++i) {
    if (!values[array[i]]) continue;
    tmp.add(array[i]);
    values[array[i]] = false;
  }
  return tmp;
};

/// 現在の配列から、指定された配列に含まれる要素と同一の要素を除いた配列を得る(差集合)
/// @param array 対象となる配列
/// @return 指定された配列の要素を取り除いた配列
Array.except = function(array) {
  var values = %[];
  for (var i = array.count-1; i >= 0; --i) {
    values[array[i]] = true;
  }
  var tmp = [];
  for (var i = 0; i < this.count; ++i) {
    if (values[this[i]]) continue;
    tmp.add(this[i]);
    values[this[i]] = true;
  }
  return tmp;
};

/// 比較関数を使用して2つの配列が同一か返す
/// @param array 比較対象となる配列
/// @param comparer 要素を比較する関数
/// @return comparerが全ての呼び出しに対してtrueを返したらtrue
Array.sequenceEqual = function(array, comparer) {
  if (this.count != array.count) return false;
  for (var i = this.count-1; i >= 0; --i) {
    if (!comparer(this[i], array[i])) return false;
  }
  return true;
};

/// 最初に条件を満たす要素を得る
/// @param predicate 条件を判定する関数
/// @param value 条件を満たす要素が存在しなかったときに返す値
/// @return 最初から見ていって最初にpredicateを満たす要素
Array.first = function(predicate, value=void) {
  for (var i = 0; i < this.count; ++i) {
    if (predicate(this[i], i)) return this[i];
  }
  return value;
};

/// 最後に条件を満たす要素を得る
/// @param predicate 条件を判定する関数
/// @param value 条件を満たす要素が存在しなかったときに返す値
/// @return 最後から見ていって最初にpredicateを満たす要素
Array.last = function(predicate, value=void) {
  for (var i = this.count-1; i >= 0; --i) {
    if (predicate(this[i], i)) return this[i];
  }
  return value;
};

/// 連続した値が要素となった配列を生成
/// @param start 開始する値
/// @param count 生成する数
/// @return start + i(0...count)の配列
Array.range = function(start, count) {
  var tmp = [];
  for (var i = count-1; i >= 0; --i) {
    tmp[i] = start + i;
  }
  return tmp;
};

/// 同じ値が連続した配列を生成
/// @param element 要素
/// @param count 要素の数
/// @return elementがcount個だけ入った配列
Array.repeat = function(element, count) {
  var tmp = [];
  for (var i = count-1; i >= 0; --i) {
    tmp[i] = element;
  }
  return tmp;
};

/// 空の配列を生成
/// @return 空の配列
Array.empty = function() {
  return [];
};

/// 条件を満たす要素が存在するか調べる
/// @param predicate 条件を判定する関数
/// @return 条件を満たす要素が1つでも存在すればtrue
Array.any = function(predicate) {
  for (var i = 0; i < this.count; ++i) {
    if (predicate(this[i], i)) return true;
  }
  return false;
};

/// 全ての要素が条件を満たすか調べる
/// @param predicate 条件を判定する関数
/// @return 全ての要素が条件を満たせばtrue
Array.all = function(predicate) {
  for (var i = 0; i < this.count; ++i) {
    if (!predicate(this[i], i)) return false;
  }
  return true;
};

/// 配列に特定の要素が存在するか調べる
/// @param value 検索する要素
/// @param comparer 2つの要素を受け取って、等しいか返す関数。省略時は === で比較
/// @return 指定された要素が1つでも存在すればtrue
Array.contains = function(value, comparer) {
  if (comparer === void) {
    comparer = function(first, secound) { return first === second; };
  }
  for (var i = 0; i < this.count; ++i) {
    if (comparer(value, this[i])) return true;
  }
  return false;
};

/// 配列の全要素を数値に変換し、その和を得る
/// @return 配列の全要素の和
Array.sum = function() {
  var v = 0;
  for (var i = this.count-1; i >= 0; --i) {
    v += +this[i];
  }
  return v;
};

/// 配列の全要素から最小値を求める
/// @return 配列の要素の中で最も小さい値
Array.min = function() {
  var v = this[-1];
  for (var i = this.count-2; i >= 0; --i) {
    if (v > this[i]) v = this[i];
  }
  return v;
};

/// 配列の全要素から最大値を求める
/// @return 配列の要素の中で最も大きい値
Array.max = function() {
  var v = this[-1];
  for (var i = this.count-2; i >= 0; --i) {
    if (v < this[i]) v = this[i];
  }
  return v;
};

/// 配列の全要素から平均値を求める
/// @return 配列の全要素の平均値
Array.average = function() {
  return this.sum() / this.count;
};

/// 配列の全要素を集計する
/// @param func 要素を集計する関数
/// @return 指定された関数で集計された結果
Array.aggregate = function(func) {
  var tmp = void;
  for (var i = 0; i < this.count; ++i) {
    tmp = func(tmp, this[i]);
  }
  return tmp;
};

続きを閉じる▲

タグ : 吉里吉里 TJS

カテゴリ : TJS

吉里吉里安定版と開発版の違いメモ

現在の安定版(2.32rev2)と最新開発版(2.31.2012.831, revision:5057)の違いメモ
バグが修正されたり実行速度が速くなったり機能が増えたりしているので開発版を使うべき
開発版のexeは下記URLにあります
https://sv.kikyou.info/svn/kirikiri2/trunk/kirikiri2/bin/win32/



・Scripts.execStorage(),Scripts.evalStorage(), Scripts.exec(), Scripts.eval()の引数contextを追加
Scripts.execStorage(storage, mode='', context=global)
Scripts.evalStorage(storage, mode='', context=global)
Scripts.exec(script, name='', linesof=0, context=global)
Scripts.eval(script, name='', linesof=0, context=global)
storage:評価または実行するファイル名
mode:ファイル読み込みモードを指定できます。"o"を前置した10進数値でバイト単位の読み込み開始位置を指定できます
context:実行コンテキストを指定できます
script:評価または実行するtjsスクリプト
name:エラーメッセージ中で使用するファイル名を指定できます
linesof:エラーメッセージ中で使用する行番号を指定できます
mode、name、linesofは安定版でも使えますがリファレンスでは省略されています

・System.terminate(), System.exit()の引数codeを追加
System.terminate(code=0)
System.exit(code=0)
code:終了コードを指定します
吉里吉里でツール作るときに便利かも

・Storages.clearArchiveCache()を追加
アーカイブのキャッシュをクリアするらしい?

・System.graphicCachLimit=gcsAutoの時のキャッシュ量計算式変更
メモリ512MB以上のときに64MB固定だったのを、物理メモリの1/10に変更
今はメモリたくさん載っているのが当たり前なのでグラフィックキャッシュ量が大幅に変わります
使用する画像が小さければ大して影響ないかもしれません

・コマンドラインオプションとして"-dsvolfactor"を追加
サウンドの音量を設定したときに、実際の聞こえる音の大きさが変わっている可能性があります
今までより直感的な音量になっているらしいです
現在の安定版と同じ動作にしたい場合は-dsvolfactor='5000'を指定

・マルチスレッド動作の軽量化

・プライマリレイヤ外でもウィンドウのマウスイベントが発生するように変更

・KAGParser.mp追加、KAGParserでのtjs式評価時のコンテキストをglobalからKAGParserオブジェクト自身に変更
説明面倒なので略

・Layer.onMouseDownの直前にLayer.onMouseMoveが必ず呼ばれるように変更

・バグ修正
>>=と>>>=が逆だったらしい
KAGParser.restoreでエラーが出る件
マルチスレット描画時のバグ
その他

・その他プラグインなど
公式レポジトリにあるプラグインは開発版基準で作られているので安定版では動かない物も
プラグインもどんどん機能増えてます

続きを閉じる▲

タグ : 吉里吉里

カテゴリ : TJS

コンテキストの話①

■吉里吉里/KAG/TJS雑談質問スレ■その25の>>815からちょっとでてたので。
そこで書かれていてることが全てですが具体例付きで丁寧な説明。


TJSリファレンス>データ型>オブジェクト型より引用
http://devdoc.kikyou.info/tvp/docs/tjs2doc/contents/types.html
「オブジェクト型は、オブジェクトを保持する型です。new 演算子で確保したオブジェクトのほか、関数、クラス、プロパティオブジェクトなどはすべてオブジェクト型になります。
typeof 演算子は、これに対しては "Object" を返します。

TJS や JavaScript と異なり、TJS2 のオブジェクト型は、オブジェクトそのものへのポインタと、それが使用されるべきコンテキストとなるオブジェクトのポインタの2つのポインタを内部に持っていて、いわゆるクロージャを実現できるようになっています。
このコンテキスト部分を変更する演算子が incontextof 演算子です。」



コンテキストがあるのはオブジェクト型の変数のみです。
「123」のような整数や「"あいうえお"」のような文字列にはコンテキストは有りません。

オブジェクトは「そのオブジェクトそのもの」と、「コンテキスト用のオブジェクト」の2つから成り立っています。
ポインタ、というのはまあどうでもいいです。


var moge = "グローバルmoge";
function hoge() {
  System.inform(moge);
}
hoge();
関数もオブジェクト型です。
コンテキストが重要になるのは大抵関数なので関数を具体例として取り上げながら説明していきます。

この「hoge」関数で説明すると、オブジェクトそのものとは「System.inform(moge);」のような中身のことです。
moge変数の中身を表示します。
コンテキスト用のオブジェクトは、グローバル関数の場合はglobalになっています。

コンテキスト(context)とは、直訳すると文脈です。
変数を探すときにどこから探してくるのか、ということです。
この関数が実行された時にmoge変数を表示するには、moge変数を探してこなければなりません。
今回はすぐ上でglobalで宣言しているmogeが見つかって「グローバルmoge」と表示されます。


var moge = "グローバルmoge";
class HogeClass
{
  var moge = "クラスのmoge";
  function hoge() {
    System.inform(moge);
  }
}
var h = new HogeClass(); // インスタンスの作成
h.hoge();
クラスに含まれる関数は、そのクラスをnewしたときに、そのnewで生成されたオブジェクトをコンテキストとして登録されます。
var h = new HogeClass();();」で、HogeClassのインスタンスが作られます。
このインスタンスにはhoge関数が含まれますが、この関数オブジェクト(h.hoge)のコンテキストはglobalではなくこのインスタンス(h)です。
hには「var moge = "クラスのmoge";」としてmoge変数も含まれています。
「h.hoge();」で関数が呼び出されると、コンテキストであるhからmogeを探し、mogeがあるのでその「"クラスのmoge"」が表示されることになります。


var moge = "グローバルmoge";
class HogeClass
{
  var moge = "クラスのmoge";
  function hoge() {
    System.inform(moge);
  }
}
var h = new HogeClass();
delete h.moge; // 関数を実行する前にh.mogeを削除
h.hoge();
先程とは変わって、h.hogeを呼び出す前に、「delete h.moge;」でhに含まれるmoge変数を削除しています。
「h.hoge();」で関数が呼び出されると、コンテキストであるhからmogeを探すところまでは同じです。
しかし、hの中のmogeは既に削除されていて見つかりません。
この場合、globalまで行って探しに行ってくれます。
すると「var moge = "グローバルmoge";」として宣言されているグローバルのmoge変数が見つかるので、「グローバルmoge」と表示されます。


function hoge() {
  System.inform(this);
}
hoge();
オブジェクトの中では、「this」を使うとコンテキストにアクセスできます。
これを実行すると、「(object 0x02878824:0x02878824)」のような、よくわからないものが表示されます。
前述のとおりhoge関数のコンテキストはglobalのはずです。それを確かめてみます。


function hoge() {
  System.inform(this == global);
}
hoge();
これを実行するとtrueを示す1が表示されます。
thisglobalは一致しています。


var moge = "グローバルmoge";
class HogeClass
{
  var moge = "クラスのmoge";
  function hoge() {
    System.inform(moge);
  }
}
var h = new HogeClass(); // インスタンスの作成
var tmp = h.hoge;
tmp(); // "クラスのmoge"が表示される。
tmp = (h.hoge incontextof global);
tmp(); // "グローバルmoge"が表示される。
incontextof演算子を使うと、コンテキストを変更したオブジェクトが得られます。
var tmp = h.hoge;」では、そのまま代入しているのでtmpとh.hogeは全く同じ物になります。
つまり、tmpとh.hogeはどちらも「System.inform(moge);」という関数の中身を持ち、「new HogeClass()」で生成されたオブジェクトがコンテキストとなっている関数オブジェクトです。

1つ目の「tmp();」では、「h.hoge();」とするのと同じで、「クラスのmoge」が表示されます。

「tmp = (h.hoge incontextof global);」ではどうなるでしょうか。
このtmpは、中身はそのまま「System.inform(moge);」のような関数ですが、コンテキストはglobalとなっている新しい関数オブジェクトです。
h.hogeとはコンテキストが違っています。
2つ目の「tmp();」では、System.inform(moge);という中身が実行されますが、mogeを探す先はglobalです。
よって「グローバルmoge」が表示されます。


var moge = "グローバルmoge";
class HogeClass
{
  var moge = "クラスのmoge";
  function hoge() {
    System.inform(moge);
  }
}
var h = new HogeClass(); // インスタンスの作成
h.hoge(); // "クラスのmoge"が表示される。
(h.hoge incontextof global)(); // "グローバルmoge"が表示される。
先程とは違って、いちいちtmp変数に代入せずにすぐに呼び出していますが、実行結果は同じになります。


var moge = "グローバルmoge";
function hoge() {
  global.System.inform(moge);
}
hoge();
var dic = %[ "moge" => "辞書のmoge" ];
(hoge incontextof dic)();
辞書もオブジェクトなのでコンテキストとして使えます。
「(hoge incontextof dic)();」ではコンテキストをdicに変更した関数を呼び出しているので、moge変数を探すときに辞書の中から探されます。
よって「辞書のmoge」が表示されます。

この時に注意なのですが、「global.System.inform(moge);」のようにglobalをつけています。
つけずに「System.inform(moge);」とすると、「(void) から Object へ型を変換できません。」のようなエラーが出ます。

「System」というのも吉里吉里側でglobalにあらかじめ宣言されているただの変数です。
実行時にはコンテキストからSystemを探します。
コンテキストがglobalの時はもちろん問題なく見つかります。
HogeClassの例のように、コンテキストがクラスインスタンスであっても、インスタンスの中に見つからなければglobalまで自動的に探しに行ってくれるので問題ありません。

しかし、コンテキストが辞書のときはそうは行きません。
存在しない変数はglobalまで探しに行かずにvoidとなってしまいます。
「System.inform(moge);」としてしまうと、Systemが見つからずにvoidとなってしまいます。
つまり「void.inform」となって、voidをオブジェクトに変換しようとして失敗するのでエラーが発生します。


コンテキストが辞書ではなく、クラスの場合でも問題が起きることがあります。
class HogeClass
{
  function HogeClass() {}

  function hoge() {
    var localH = new HogeClass();
    localH.moge();
  }

  function moge() {
    System.inform("mogeが呼ばれました。");
  }
}
var h = new HogeClass();
h.hoge();
これを実行すると、「関数ではないかプロパティの種類が違います」というエラーとなります。l
問題となるのは「var localH = new HogeClass();」です。
実行時にHogeClassをコンテキストである同じインスタンスの中から探し始めます。
このとき、コンストラクタである「HogeClass」関数が見つかってしまいます。
globalに宣言されているHogeClassクラスまで探しに行くまえに見つかってしまうのです。
関数をnewすることは出来ないのでエラーとなります。
var localH = new global.HogeClass();」のようにglobalにあるHogeClassであることを示せば正しく動作し、「mogeが呼ばれました」と表示されます。

続きを閉じる▲

タグ : 吉里吉里 TJS

カテゴリ : TJS

kwidgetがすごいらしい

吉里吉里公式レポジトリにkwidgetが追加されてました
TJSを使って本格的なGUIアプリが作れるようになります
吉里吉里用のツールとか作るのに大活躍
twitterでの解説(http://togetter.com/li/289394)

何だかんだでTJSは無駄に使い慣れてるので非常にありがたい
あと1ヶ月早くこれがあれば……


kwidget_class_diagram.jpg
ソース読んできたついでにクラス図もどき

KWindow作ってprimaryWidgetにLayout突っ込んでそれにwidget突っ込んだら大体動いてるような
今のところ説明書は無いけどサンプルがかなり詳しいのでわりと何とかなる

ウィジェットの状態は辞書にバインドしておくと勝手に保存されるっぽい
便利やなー
ボタン、スライダーなどに加えてタイムラインやカラーピッカーもあったり
欲しい物は全部ある気がする

続きを閉じる▲

タグ : 吉里吉里 TJS

カテゴリ : TJS

layerExLongExposure.dll

吉里吉里レポジトリにlayerExLongExposureプラグインが増えてた。
ルール画像とか作るのに便利らしい。

Plugins.link("layerExLongExposure.dll");


var WIDTH = 800, HEIGHT = 600// ルール画像サイズ


// ウィンドウ作成
var win = new Window();
win.setInnerSize(WIDTH, HEIGHT);


// レイヤ作成
var layer = new Layer(win, null);
layer.setSize(WIDTH, HEIGHT);


// ルール画像作成
with (layer) {
  .initExposure(); // バッファ初期化

  var boxWidth = 20, boxHeight=20// 矩形のサイズ
  var opacity1 = 255// ■部分の透過度
  var opacity2 = 230// □部分の透過度
  
  // レイヤに矩形をしきつめる
  for (var i = 0; i*boxHeight < HEIGHT; ++i) { // 一番上の行から順に矩形をしきつめる
    if (i&1) { // i が奇数のとき(奇数行)
      for (var j = 0; j*boxWidth < WIDTH; ++j) {
        // ■□■□■□■□……と矩形塗りつぶし
        .colorRect(j*boxWidth, i*boxHeight, boxWidth, boxHeight, 0x000000, (j&1) ? opacity1 : opacity2 );
      }
    } else { // i が偶数のとき(偶数行)
      for (var j = 0; j*boxWidth < WIDTH; ++j) {
        // □■□■□■□■……と色塗り
        .colorRect(j*boxWidth, i*boxHeight, boxWidth, boxHeight, 0x000000, (j&1) ? opacity2 : opacity1 );
      }
    }
    .snapExposure(); // バッファに加算
  }
  
  .copyExposure(void); // レイヤ上に画像作成
  .termExposure(); // バッファ廃棄
}


// 画像保存
layer.saveLayerImage(System.exePath + "rule.bmp");


// 終了
System.inform("画像を保存しました");
System.terminate();
とりあえずこれで↓の画像ができる。
単純だけどなかなか便利。
layerExLongExposureSample.png

続きを閉じる▲

タグ : 吉里吉里 TJS

カテゴリ : TJS

alt+enter でウィンドウ切り替え

Alt+Enter でフルスクリーンにできるゲーム結構あるよね。
KAG/KAGEXでのやりかた。

KAGEXの場合
// Alt+Enter でウィンドウ切り替え
kag.menu.add(kag.fullScreenShortcut = new KAGMenuItem(this, "fullScreenShortcut", 0, function() {
    changeFullScreenOrWindow(!fullScreened);
} incontextof kag, false));
kag.fullScreenShortcut.shortcut = "Alt+Enter";
kag.fullScreenShortcut.visible = false;


KAGの場合
// Alt+Enter でウィンドウ切り替え
kag.menu.add(kag.fullScreenShortcut = new KAGMenuItem(this, "fullScreenShortcut", 0, function() {
    if (fullScreened) {
        onWindowedMenuItemClick();
    } else {
        onFullScreenMenuItemClick();
    }
} incontextof kag, false));
kag.fullScreenShortcut.shortcut = "Alt+Enter";
kag.fullScreenShortcut.visible = false;


afterinit.tjs にでも書いておくと Alt+Enter で切り替えできるようになります。

続きを閉じる▲

タグ : 吉里吉里 KAGEX KAG TJS

カテゴリ : TJS

TJSでKAGのタグを実行する(kag.tagHandlers)

TJSでKAGのタグを実行する方法。
kag.tagHandlers編。

基本的にはkag.tagHandlersにタグの実装が入っています。
コンダクタからもkag.tagHandlersが呼び出されています。
@iscript
// image タグを layer=0 storage=image.png visible=true で呼び出してみる。
kag.tagHandlers.image(%["layer" => "0""storage" => "image""visible"=>"true"]);
@endscript
属性は引数に辞書配列の形で指定します。
KAGでは属性の値は全て文字列の形になるので、ここでも文字列で指定することを推奨します(「"layer"=>0」 ではなく 「"layer"=>"0"」)。


ただしこの方法では使えないタグもあります。

kag.tagHandlersに存在しないタグは使えません。
[macro][endmacro][if][else][elsif][endif][ignore][endignore][iscript][endscript][emb][erasemacro][jump][call][return]
これらは特殊な処理を行うのでkag.tagHanlersからは使えません。
※使う方法はまた今度紹介します。

[wait][wt]などのウェイトが入るタグも使えません。
KAGでウェイトを入れる、というのはコンダクタの実行を一時停止するということでした。
kag.tagHandlersから直接呼び出しても、コンダクタの動作には影響を与えません。
ウェイトを入れる(= コンダクタの実行を一時停止する)にはコンダクタを通してタグを使う必要があります。
※この方法もまた今度紹介します。

時間をおいてTJSを実行するには全く別の方法を使います。
KAGとTJSは全く別物です。
[wait]などKAGの機能を使ってTJSの動作は止めることはできないので注意してください。
@iscript
System.inform("てすと");
// ここで3秒待ちたい、というときには[wait]などは使えません。
// Timer などのKAGとは別の機能を使わなければなりません。
System.inform("3秒経ちました");
@endscript

続きを閉じる▲

タグ : 吉里吉里 TJS KAG KAGEX

カテゴリ : TJS

jump と call

[jump](KAGEXでは[next])とか[call]とか右クリックサブルーチンとかの話。

jumpタグ
シナリオの実行位置を変更します。
昨日のコンダクタの話でいうと「次のタグを探す」の探す位置を指定したラベルの位置に変更しています。
TJSだとkag.processを使っても同じことが出来ます。
kag.processでは[s]や次のタグがなかったなどの理由でコンダクタが止まっていた場合、コンダクタを再開する働きもします。

callタグ
基本的には[jump]と同じです。
ただし[return]タグを使うと[call]タグの位置に戻ってくることができます。
これは何をしているかというと、[call]タグを実行する時にコンダクタがその場所を記録しておきます。
そして[return]でその記録した位置にジャンプで戻り、使った記録を削除します。
なので[call]と[return]の数が合わないと、その記録情報が無かったり([return]が多い)、記録情報が使われずに残ってしまったり([return]が足りない)して困ったことになります。

右クリックでcall
[rclick]を使うと右クリックの動作を設定することができます。
jump=true のときには通常の[jump]タグと同じような動作ですが、 call=true のときには[call]とはちょっと違った動作をします。
call=true のときに右クリックされると、 storage, target 属性に指定された位置へシナリオ実行位置を変更します。
この変更には、[call]や[jump]とは異なり、通常のコンダクタ(kag.mainConductor)は使われません。通常のコンダクタは右クリックされた位置で一時停止しておいて、右クリックサブルーチン用のコンダクタ(kag.extraConductor)を使用します。
右クリック用のコンダクタの動作自体は通常のコンダクタとほぼ同じです。
[rclick]で指定された位置からシナリオ実行を開始します。そして[rclick]のcallと対応する[return]で停止して、一時停止していた通常のコンダクタの実行を再開します。

このことは普段は意識する必要はありませんが、右クリックサブルーチン内と外で使われるコンダクタが異なるために、困ることもあります。
右クリックサブルーチンから既読スキップはその例です。

続きを閉じる▲

タグ : 吉里吉里 KAGEX KAG TJS

カテゴリ : TJS

簡単なコンダクタの話

TJSで書かれたKAGはKAGスクリプトを解析して実行しなければなりません。
その解析、実行を行うのがコンダクタです。以下の話はKAGEXでも共通です。


コンダクタの中身はConductor.tjsに書かれています。
コンダクタは「次のタグを探す」→「見つけたタグを実行」を延々と繰り返しています。

KAGスクリプトでコンダクタがやっていることのイメージを書くと
*START
; 次のタグを探す
; 見つけたタグを実行
@jump target=*START

*END
; 指定時間後に*STARTにジャンプする命令
@s
こんな感じです。実際にはコメント部分で色々とやってくれています。
*ENDはウェイトを入れるタグを実行するときにジャンプしてくるラベルです。
KAGの「ウェイト」というのはコンダクタの実行を一時停止することです。
指定時間後にまた*STARTから実行再開します。

例えばfirst.ksに以下のようなスクリプトを書いて実行します。
[wait time=200]
*start|スタート
[cm]
こんにちは。
最初のタグは[wait]です。
初っ端からウェイトが入ったので*ENDにジャンプして200ms後に*STARTにジャンプします。
次のタグは[cm]です。ラベルの処理はここでは説明しません。
[cm]でメッセージレイヤがクリアされました。
ウェイトはないのですぐに*STARTにジャンプ、次のタグを探します。
次のタグは「こ」です。もっと言うと[ch text="こ"]です。
文字は自動的に[ch]タグとして処理されるようになっています。
まずは「こ」をメッセージレイヤに表示します。
次にウェイトを入れます。ウェイトの時間は設定された文字表示速度です(デフォルト「普通」で30ms)。
なので*ENDに飛んで30ms後に*STARTにジャンプします。
おなじことを「ん」「に」「ち」「は」「。」について繰り返します。

「。」の次には何もタグがないので*ENDに飛んで、*STARTへはジャンプせずにそのまま終わりです。
ここでは[wait][cm][ch]タグしか出てきませんでしたが他のタグでも「探す」→「実行」を繰り返す部分は同じです。
※それぞれのタグの動作の中身は基本的にkag.tagHandlersにあります。

続きを閉じる▲

タグ : 吉里吉里 KAGEX KAG TJS

カテゴリ : TJS

TJS小ネタ - foreach

tjsの小ネタ。駄文。foreachの解説。

配列を順に処理するには↓のようにforで添え字をまわします。
var array = [0102030405060708090];

for (var i = 0; i < array.count; i++) {
    Debug.message(array[i]);
}
配列arrayの中身を順にDebug.message関数でコンソールに表示するだけのスクリプトです。
0, 10, 20, ..., 90まで表示されます。

ついでに逆順に処理する例
var array = [0102030405060708090];

for (var i = array.count-1; i >= 0; i--) {
    Debug.message(array[i]);
}
先ほどとは逆に90, 80, 70, ..., 0まで表示されます。


foreachの使い方(1)
辞書配列でもおなじように順に処理したい場合はどうするか。
KAGEXのUtils.tjsにそのためのの関数としてforeach関数が定義してあります。
以下該当部分をコピペ。
/**
 * 辞書の総なめ処理
 * @param dict 辞書
 * @param func 処理ファンクション func(name, value, dict);
 */

function foreach(dict, func, *) {
    if (dict) {
        var names = [];
        names.assign(dict);
        for (var i = 0; i < names.count; i += 2) {
            func(names[i], names[i+1], dict, *);
        }
    }
}
第1引数dictに処理したい辞書配列をわたします。
第2引数funcに辞書配列のそれぞれのペアを処理する関数をわたします。

とりあえず使ってみます。
/* スクリプト例(1) */

// 適当な辞書配列
var hoge = %[
    "first" => 1,
    "second" => 2,
    "third" => 3,
];

// 処理関数
function fuga(name, value, dict) {
    Debug.message("dict[" + name +"] の中身は「" + value + "」です。");
}

// foreachを使う
foreach(hoge, fuga);
これを実行すると以下のように表示されます。
dict[first] の中身は「1」です。
dict[second] の中身は「2」です。
dict[third] の中身は「3」です。
foreachの第2引数にわたした関数(ここではfuga関数)にはいくつか引数がわたされます。
第1引数(name)に添え字の名前が、第2引数(value)にはそれに対応する値がわたされます。
第3引数(dict)は使っていませんが、foreachの第1引数にわたした辞書配列がそのままわたされています。
第1引数と第2引数の内容が順に変わりながら、辞書配列のペアの数だけfuga関数が呼ばれます。
fuga("first"1, hoge);
fuga("second"2, hoge);
fuga("third"3, hoge);
というのを勝手にやってくれるのがforeach関数です。

foreachの使い方(2)
第3引数まではforeachの定義で決められていますが、第4引数以降には好きな引数をわたすことができます。
foreachの第3引数以降にわたしたものが、第4引数以降にそのままわたされます。

以下使ってみるスクリプト。
/* スクリプト例(2) */

// 適当な辞書配列
var hoge = %[
    "first" => 1,
    "second" => 2,
    "third" => 3,
];

// 処理関数
function fuga(name, value, dict, test1, test2) {
    Debug.message("dict[" + name +"] の中身は「" + value + "」です。");
    Debug.message("第4引数の値は:" + test1 + ", 第5引数の値は:" + test2);
    // ↑Debug.messageをもう1行追加
}

// foreachを使う
foreach(hoge, fuga, "test1にわたる値""test2にわたる値");
// ↑第3引数以降にも値をわたす
変更点は、
・関数fugaに引数test1とtest2を追加し、Debug.messageで表示するようにする。
・foreachの第3引数と第4引数に文字列をわたすようにする。
の2点のみです。

これを実行すると以下のように表示されます。
dict[first] の中身は「1」です。
第4引数の値は:test1にわたる値, 第5引数の値は:test2にわたる値
dict[second] の中身は「2」です。
第4引数の値は:test1にわたる値, 第5引数の値は:test2にわたる値
dict[third] の中身は「3」です。
第4引数の値は:test1にわたる値, 第5引数の値は:test2にわたる値

ここではforeachは以下のように処理関数fugaを呼び出しています。
fuga("first"1, hoge, "test1にわたる値""test2にわたる値");
fuga("second"2, hoge, "test1にわたる値""test2にわたる値");
fuga("third"3, hoge, "test1にわたる値""test2にわたる値");


実際にKAGEXで使われている部分を見るとhogeの部分は式中関数としてわたされていることが多いです。
見た目としては↓のようになります。
/* スクリプト例(3) */

// 適当な辞書配列
var hoge = %[
    "first" => 1,
    "second" => 2,
    "third" => 3,
];

// foreachを使う
foreach(hoge, function(name, value, dict) {
    Debug.message("dict[" + name +"] の中身は「" + value + "」です。");
});
実行結果はスクリプト例(1)とおなじになります。

使い方の説明は以上です。次はforeachのスクリプトを解説します。

foreachの仕組み
行番号を振って再掲。
0: function foreach(dict, func, *) {
1:     if (dict) {
2:         var names = [];
3:         names.assign(dict);
4:         for (var i = 0; i < names.count; i += 2) {
5:             func(names[i], names[i+1], dict, *);
6:         }
7:     }
8: }
0行目はただの関数宣言です。
1つ目の引数dictは処理したい辞書配列、2つ目の引数funcは処理関数でした。
3つ目の引数の*は見慣れないかもしれません。
リファレンス参照→http://devdoc.kikyou.info/tvp/docs/tjs2doc/contents/function.html#id214
4行目を見るとfuncの第4引数に*が指定されています。
ここではforeachの第3引数以降をfuncの第4引数以降にわたす機能を果たしています。

1行目はdictがvoidのときには何もしない、程度だと思います。

2行目はnamesに配列を生成しています。

3行目でその配列にわたされた辞書配列dictをコピーしています。
リファレンス参照→http://devdoc.kikyou.info/tvp/docs/tjs2doc/contents/array.html#id14
辞書配列を配列にコピーすると、「名前、値の順に交互に現れる配列」になります。
例えば引数にわたされたdictが
dict["first"] = 1;
dict["second"] = 2;
dict["third"] = 3;
だったとき、配列namesは
names[0] = "first";  // ←1つ目のペアの名前
names[1] = 1;        // ←1つ目のペアの値
names[2] = "second"// ←2つ目のペアの名前
names[3] = 2;        // ←2つ目のペアの値
names[4] = "third";  // ←3つ目のペアの名前
names[5] = 3;        // ←3つ目のペアの値
のようになります。
添え字が偶数の部分にペアの名前、奇数の部分にペアの値が入っている形です。

4,5,6行目のforループで実際にわたされた処理関数funcを呼び出します。
最初のループではi=0です。
このとき「func(names[0], names[1], dict, *);」として呼びだされます。
names[0]="first", names[1]=1だったので使い方の欄で説明したとおりに引数がわたされています。
次のループに入るときにはiに2を足すことで次のペアをわたしてfuncを呼び出します。
2周目のループでは「func(names[2], names[3], dict, *);」となるわけです。
これを最後のペアまで繰り返します。

続きを閉じる▲

タグ : 吉里吉里 KAGEX TJS

カテゴリ : TJS

TJSからKAGスクリプトを実行する。

■吉里吉里/KAG/TJS雑談質問スレその13の767にあるものですがここでも紹介しておきます。
TJSからKAGスクリプトを実行する方法。
TJSでKAGのタグを実行する方法としては「kag.tagHandlers.backlay(%[]);」のようにtagHandlersを使う方法が有名です。
がこれだとマクロなど実行できないものがあります。
今回の方法だとそれらのタグも実行できます。

AfterInit.tjsなどに↓のスクリプトを貼りつけておきます。
kag.eval = function(scenario) {
    var tmp = onConductorScenarioLoad;
    onConductorScenarioLoad = function(name){ return name; };
    process("\n"+scenario, "");
    onConductorScenarioLoad = tmp;
incontextof kag;

使うときは最初の引数にKAGスクリプトの文字列をわたしてやればいいです。
@iscript
kag.eval("
*start
@cm
KAGスクリプトのサンプルです。[l][r]
コンダクタでそのまま実行されるので何でも書けます。[l][r]

; マクロ定義してみる。chタグで文字表示するだけのマクロ
@macro name=テスト
@ch text=%text
@endmacro

@テスト text=あいうえお

@r

jumpタグで最初に戻ります。[l][r]
@jump target='*start'
");
@endscript
動作としてはkag.evalにわたした文字列の最初に[jump]でジャンプしてくる感じになります。
なのでkag.evalは[s]などで止まってるときなどに実行した方がいいでしょう。
他の注意点としては、文字列でわたすために""(ダブルクォーテーション)でわたしているので中では使えません。
例では「target='*start'」のように''(シングルクォーテーション)を使ってみました。

もっと単純な例としてただマクロを実行してみる。
@iscript
kag.eval("[テスト text=マクロを使ってみる][s]");
@endscript

続きを閉じる▲

タグ : 吉里吉里 KAG TJS 講座

カテゴリ : TJS

右クリックサブルーチンから既読スキップ

コンフィグ画面サンプル作成中なう。

既読スキップさせるには
[eval exp="kag.skipToStop()"]
を使えばいいのですが右クリックサブルーチン内からは使えないらしい困った。

何でなのかは見てないので知らん。
とりあえずサブルーチン抜ける直前にトリガ引いてskipToStop()呼び出したら上手くいった。
大丈夫なのかこれ。

んで調べてたら解決法発見↓
吉里吉里/KAG小技集
restoreFromRightClickなんてあったのか不覚。
これと同じ感じでやります。


追記
トリガ引くやり方だと駄目ですね。
実行されるタイミングによっていくらでもおかしなことになります。

続きを閉じる▲

タグ : 吉里吉里 KAG TJS

カテゴリ : TJS

演算子の優先順位一覧

TJSの演算子の優先順位。
1が一番高く、17が一番低いです。
一応載せておきますが特に意味なし。

1.() [] . 後置++ 後置-- 後置! incontextof int real string
2. 前置! ~ 前置++ 前置-- new invalidate isvalid delete typeof # $ 単項+ 単項- 単項* instanceof
3. % / \ *
4. + -
5. >> << >>>
6. < > <= >=
7. == != === !==
8. &
9. ^
10. |
11. &
12. &&
13. ||
14. ? :
15. = <-> &= |= ^= -= += %= /= \= *= &&= ||= >>= <<= >>>=
16. ,
17. if

続きを閉じる▲

タグ : 吉里吉里 TJS

カテゴリ : TJS

最新記事
カテゴリ

openclose

記事一覧
Twitter
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。