// %desc App.Lex の内容を出力します // %menu App.Lex 出力 // 再描画を抑制します var ctx = App.Caret.BeginUpdate(); try { // 操作グループを開始します App.Caret.BeginOperateGroup('マクロ:App.Lex 出力'); try { // 対象の Lex オブジェクトを選択 var msg = ''; for (var i = 0; i < App.Lexes.Count; i++) { msg += i + ": " + App.Lexes.Item(i).Name + '\n'; } // ポップアップで選択 var ret = App.PopupMenu('mouse', 'left top', msg); if (ret == -1) { // キャンセル return; } var lex = App.Lexes.Item(ret); // dot 形式の文字列作成 var cmd = lex_to_dot(lex); // エディタに出力するならこんな感じ。 //App.Caret.Send(cmd); // ポップアップするならこんな感じ。 //App.Alert(cmd); // ファイルに保存 var outfile // .dot で保存するならこんな感じ。 //outfile = App.Path + lex.Name + ".dot"; //savetext(outfile, cmd); // .png で保存 outfile = App.Path + lex.Name + ".png"; save_as_png(outfile, cmd); } finally { // 操作グループを終了します App.Caret.EndOperateGroup(); } } catch (e) { App.Alert("catched: " + e); } finally { // 再描画を許可します App.Caret.EndUpdate(ctx); } function lex_to_dot(lex) { var nodes = []; // ノード生成の要/不要を示す配列 var result; // dot 形式の文字列 // dot の先頭部分 result = "digraph g {\n"; result += 'label="' + lex.Name + '"\n'; // 左→右 方向で描画したい場合は下の行を uncomment してください。 //result += 'rankdir="LR"\n'; // 各 Rule 毎の処理 for (var i = 0; i < lex.Count; i++) { //----------------- // Pattern の解析 //----------------- var pattern = lex.Item(i).Pattern; var matches = pattern.match(/^([^\/]*)(\/.*\/i?)$/); if (matches == null) { throw("invalid pattern: " + pattern); } var state_exp = matches[1]; var pattern_raw = matches[2]; if (state_exp == '') { state_exp = '1..30'; // 状態のリストを省略した場合は「どの状態のときも照合」 } var states_from = expand_states(state_exp); // 出力用にエスケープ pattern_raw = pattern_raw.replace(/\\/g, '\\\\') pattern_raw = pattern_raw.replace(/"/g, '\\"') //----------------- // Style の解析 //----------------- var style_exp = lex.Item(i).Style; var style = expand_style(style_exp); var state_to; if (style.transit == '*return*') { state_to = "31"; // acccepting state は 31 で表す } else { state_to = (style.transit || style.state); if (!state_to) { continue; // 遷移しない場合は出力なし } } // 各遷移を出力 for (var j = 0; j < states_from.length; j++) { var state_from = states_from[j]; var state_to_tmp = state_to; if (state_to.toString().match(/^[+-]\d+$/)) { state_to_tmp = eval(state_from+state_to); } // TODO: color なんかを元に矢印に色をつけたいところ。 // でもほとんどが exstyle で定義されている&.ini ファイルの読み込みは面倒なので、とりあえず未実装。 result += '"' + nodename(state_from) + '" -> "' + nodename(state_to_tmp) + '" [ label = "' + compact(pattern_raw) + '" ];' + "\n"; nodes[state_from] = 1; // ノード生成する旨をマーク } nodes[state_to] = 1; // ノード生成する旨をマーク } // 各ノードを出力 for (var n in nodes) { if (n.match(/^\d+$/) == null) { continue; } if (n == "31") { result += '"' + nodename(n) + '" [ label = "", shape = "doublecircle" ];' + "\n"; } else { // TODO: DefaultStyle を適用したいけど、なぜか lex.Defaultstyle(n) が取得できない。萌ディタのバグ? result += '"' + nodename(n) + '" [ label = "' + n + '", shape = "circle" ];' + "\n"; } } // dot の末尾部分 result += "}\n"; return result; } // 入力(pattern_exp): "1..3,5,7..9" みたいな文字列 // 結果: ["1", "2", "3", "5", "7", "8", "9"] みたいな配列 function expand_states(pattern_exp) { var states = []; var ranges = pattern_exp.split(/,/); for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; var matches = range.match(/^(\d+)\.\.(\d+)$/); if (matches != null) { // "1..3" みたいな表現 var from = matches[1]; var to = matches[2]; if (from > to) { throw("invalid expression: " + pattern_exp); } for (var s = from; s <= to; s++) { states.push(s); } } else { // "5" みたいな表現 states.push(range); } } return states; } // 入力(style_exp): "color:red;background-color:white;state:2" みたいな文字列 // 結果: ["color" => "red", "background-color" => "white", "state" => "2"] みたいな連想配列 function expand_style(style_exp) { var style = []; var ranges = style_exp.split(/;\s*/); for (var i = 0; i < ranges.length; i++) { var range = ranges[i]; var matches = range.match(/^([^:]+):(.+)$/); if (matches != null) { // "color:red" みたいな表現 var key = matches[1]; var value = matches[2]; style[key] = value; } else { // "nostyle" とか style[range] = range; } } return style; } // dot で使う node 名を返す。(数字だったら先頭に 'n' を付加するだけ) // node 名の先頭に数字を使えないために必要。 function nodename(state) { if (state.toString().match(/^\d+$/)) { return 'n' + state; } else { return state; } } // 出力用に短い文字列を生成する function compact(target) { if (target.length < 30) { return target; } else { return target.substring(0, 15) + " ... " + target.substring(target.length - 15); } } // dot 形式の文字列から .png を生成して保存 function save_as_png(outfile, cmd) { // WinGraphviz.DOT の生成 var gv = new ActiveXObject('WinGraphviz.DOT'); // PNG で保存 var png = gv.ToPNG(cmd); png.Save(outfile); App.Alert("Saved as " + outfile); } // dot 形式の文字列から .svg を生成して保存。 // なんか余計な HTMLEncode が入るけどだいたいこんな。 function save_as_svg(outfile, cmd) { // WinGraphviz.DOT の生成 var gv = new ActiveXObject('WinGraphviz.DOT'); // SVG で保存。 var svg = gv.ToSVG(cmd); savetext(outfile, svg); App.Alert("Saved as " + outfile); } // dot 形式の文字列から .ps を生成して保存。 // gsview とかでうまく開けないけどだいたいこんな。 function save_as_svg(outfile, cmd) { // WinGraphviz.DOT の生成 var gv = new ActiveXObject('WinGraphviz.DOT'); // PS で保存。 var ps = gv.ToPS(cmd); savetext(outfile, ps); App.Alert("Saved as " + outfile); } // 文字列をファイルに保存 function savetext(outfile, text) { var fso = new ActiveXObject("Scripting.FileSystemObject"); var ts = fso.OpenTextFile(outfile, 2, true); // ForWriting ts.WriteLine(text); }