その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

Validation Nightやります

ちょっと色々立て込んでいて,テンプレートエンジンNightやりましたよエントリを書きそびれてしまったんですが,
テンプレートエンジンやりました! 発表者の皆様,参加者の皆様まことにありがとうございました!

さて,きたる12/4 (木) ですが,前回のテンプレートエンジンNightでお知らせしたとおりValidation Nightを開催します.

Validation Night - connpass
Validationに関する知見が惜しげも無く披露されることが予想されますので,皆様ふるってご参加下さい!

LTで発表してくださる方も募集中ですので,そちらの方もよろしくお願いします.

文字列内のバックスラッシュを区別したい的な

タイトルが適当すぎて何を言っているのかわからないと思いますが,まあそういう事がごくごく稀にあります.結論から言うとヒアドキュメント使えば良いです.

以下の様な文字列を考えた時,

my $string = '\n\\';

これを1文字ずつ処理しようとすると,

say $_ for split //, $string;
# 以下出力
# \
# n
# \

という風になります.

さてこうした時,エスケープの為のバックスラッシュと純粋なバックスラッシュ (つまりエスケープされたバックスラッシュ) との区別がつかなくて困るということがごく稀に生じます.エッ,生じない? 俺はRegexp::Lexerで生じたんだよ!!!!

で,困ったので以下のように解決しました.

use B;

# 文字列をregexp quotedな変数にする
my $string = qr(\n\\);

# B::cstringでバックスラッシュをさらにエスケープする
my $cstring = B::cstring($string); # => '"(?^:\\n\\\\)"'

# 先頭の `"(` と末尾の `)"` が邪魔なので消しておく
$cstring = substr(substr($cstring, 2), 0, -2);

# B::cstringはダブルクォート文字もエスケープするのでそれは元に戻しておく
$cstring =~ s/\\"/"/g;

# 正規表現のmodifierが邪魔なので消す
$cstring =~ s/\A[?]([^:]*)://;

# 余計なバックスラッシュをまとめる
$cstring =~ s/\\\\/\\/g;

と処理してやって,一文字ずつ処理してやると

\
n
\
\

という具合にめでたくエスケープの為のバックスラッシュと純粋なバックスラッシュが区別できるという塩梅です.
reqexp quotedな変数にする*1 というのとB::cstring()を使う*2 という方法に至ったのですが,実際にはヒアドキュメントを使うと楽.

my $string = <'...';
\n\\
...
say $_ for split //, $string;
# \
# n
# \
# \

ヒアドキュメントを使うとこういう七面倒臭いことをしなくても済みます!!!
ヒアドキュメントを使える場面では使ったほうが良さそう! とは言え色々事情はあると思うので適宜使い分けという感じで.

*1:今回はたまたま解析対象が正規表現だからこれでも問題なかったけど……

*2:ちなみにB::cstring()を使っているのは文字列中の改行とかに対応するため

Regexp::Lexerってやつを書いた

https://metacpan.org/pod/Regexp::Lexer
https://github.com/moznion/Regexp-Lexer


Perl正規表現を解析したいなー,と思った時にまず目につくのはRegexp::Parserだと思うのですが,このモジュールは解釈できないメタ文字や構文が多い為,実用にあたってはかなり厳しいという印象があります (モンキーパッチなんかを当てまくると使えるかなって感じ).
ついでに言うと,メンテナのTODDR氏がここで言っているように,氏はこういった分野にあまり明るくないらしく,積極的なメンテナンスが成される可能性は高くありません.パッチを送ろうとも思いましたが,結構複雑なことをやっているモジュールなのでモチベーションがもげた……


で,Regexp::Parserで頑張るのつらいし,それほどもりもりで機能要らないから自分の欲しい機能だけ搭載したミニマルな解析器が欲しいよね〜ってなった時に,まあとにかく自分で書くしかねえかなーと思って,ひとまずその前段階としてRegexp::Lexerというモジュールをしたためました.
名前の通り,正規表現文字列 (正確にはregexp quotedな文字列) を食べて,それをトークン列に分割して返してくれる君です.


このモジュールが吐き出すトークン列を煮るなり焼くなりして自分だけの解析器を作って育てて戦わせれば良いのでは,という思想のもとに生まれました.
とは言えやはりパーザ実装があると便利なので,次はパーザを書きたいという気持ちでいっぱいですが,Regexp::Parserという名前空間を取られているのでどうしたもんかと悩んでいる今日このごろです.何か良い名前あったら教えて下さい.

location-utilってやつ書いた

JavaScriptのライブラリです.

npmとbowerにも出しておいた.
https://www.npmjs.org/package/location-util


URLを食べて,

  • よしなにURLの一部分だけ抜き出したり
  • よしなにURLの一部分を書き換えたり
  • よしなにURL組み立てなおしたり

出来る君です.使い方などはSynopsis読めば大体理解して頂けるのではないかと思います.

Angular.jsの$locationが便利っぽいなーと思って,似たような機能を手で書いてみた次第.

他に対する依存が無いのと,ブラウザでもnode.jsでも使えるのでまあまあ便利かなと思います.とにかく自分が欲しかったから書いた……

追記

というか,

という極めて優れたモジュールがあるので,まずこれを使うと良いと思います.

location-utilはAngularが提供しているようなインターフェースをパクって,いくらかパワフル (?) なメソッドを余分に提供しているんですが,それが必要なかったらmicro-locationが良いと思います.趣味の話っぽい.

追記2

あと,location-utilはプロトコル (http:// みたいな) を省略したURLでも扱えるという裏機能があるんですけど推奨はしません!!!

トークナイザーとパーザーについて,結合するということについて

トークナイザーとパーザーについて,それに準ずる物を書いていて,その最中ふと思った事について記す.

基本的にトークナイザーは対象となる文字列を,或る最小限の意味を持つトークンに分解してトークン列を導出するというのが責務で,これのみに着目するのであれば実装は (それほどまで) 難しいものにはならない *1
厄介なのはパーザーの方で,パーザーはトークナイザーが吐き出したトークンを組み合わせてある意味のある文節というか文脈というか,そういう感じの扱うのに際して便利な単位に落としこむのが仕事なのだけれど,こちらの方は様々な組み合わせが存在するために複雑になる.伴ってコードの量も多くなる.圧倒的にトークナイザーを書くよりも実装のコストが高い.
そうした状況を解決する為に,愚鈍なトークナイザーを賢いトークナイザーにしてしまうという方法が考えられる.トークナイザーが単純な文字列の分解だけではなく,その過程で附随的な情報をトークンに付け加えたり,或いはトークナイザーのレベルでいくつかのトークンをひとまとめにしてしまうというような「賢い」処理を行い,トークナイザーの成果物をパーザーフレンドリーにすることで,パーザーの実装コストを緩和するという方法だ.
これは大抵の場合,トークナイザーとパーザーが完全に独立している時の総合コストよりも低くなる感じがするんだけど,そうなるとトークナイザーとパーザーが結合を始めてしまい,トークナイザーを別の所で使ったり,或いは逆でパーザーのトークナイズエンジンを変えたりすることがままならなくなる.
と,ここらへんトレードオフだと思っていて,まあよく考えたほうが良いですよねという話で,実際誰も使わないようなトークナイザー・パーザーだったら密結合させてエイヤで実装したほうが良いと思う.逆に超有用 (だと思われるよう) なものの場合はやっぱり双方独立させたほうが良いのだろうとも思うけど,そこまで有用なものなのだったら書かずとも既にこの世に存在している可能性が高いし,もしかしたら密結合でソイヤと実装してしまってもどこからともなく神コミッターが現れて一晩で結合をひっぺがしてくれるかも知れないのでとにかく書いてしまうというのが重要である.

*1:例外はあると思う

Excel::Writer::XLSXのset_optimization()について

基本的にExcel::Writer::XLSX使うときは

my $workbook = Excel::Writer::XLSX->new('yabai.xlsx');
my $worksheet = $workbook->add_worksheet();
...

みたいな感じで使うと思うんですが,素朴にこれでやっていこうとするとExcelの行数が増えた時にメモリをバカ食いしてしまって,最悪の場合死に至ります.実際さっき死んだ.

そこでどうするかというと,set_optimization()というメソッドadd_worksheet()する前に呼んでやると良いです.

my $workbook = Excel::Writer::XLSX->new('yabai.xlsx');
$workbook->set_optimization();
my $worksheet = $workbook->add_worksheet();
...

PODにも

The "set_optimization()" method is used to turn on optimizations in the
Excel::Writer::XLSX module. Currently there is only one optimization
available and that is to reduce memory usage.

とあるように,現在のところ使用メモリ量を削減するための手立てはこれしか無いので使うほかないです.

トレードオフとしては,「既にセルに書き込まれているデータについて操作するいくつかの処理が動かない」というのがあって,これはぶっちゃけテーブル関係の処理が動かなくなるという意味なんですが,実際のところPerl経由でテーブル関係の処理とか滅多に使わないし,万が一使う事になってもそれはその時になってから考えればよろしいので,とりあえず何も考えずにset_optimization()を呼んでおけば良いという認識です.行数が多い時は速度も速くなるようですし

とにかく重要なのは,ドキュメントにあるようにadd_worksheet()する前にset_optimization()しなければならないということです.逆だと最適化が働かないので.

JavaScriptで\x特殊文字と同等の事を\xを使わずに実現する

JavaScriptの文字列中では\xという特殊文字が利用できます.ドキュメントによると,

\xXX 00 から FF までの 2 桁の 16 進数 XX で指定された、Latin-1 エンコーディングの文字。例えば、\xA9 は著作権記号を示します。

とのことで,まあここに書いてある通りの動作をします.

例:

"\xa9";
// => "©" 


ところでこの\xは文字列連結等では利用できない,つまり

"\x" + "a9";
// => ERROR!

という風には使えないわけです.そりゃそうだ.
どっこい変数の中身を\xに適用したい時などに困るわけですね.


そういう時は以下のようにすることで代用が可能です.

var charCode = "0x" + "a9"; // <= これは文字列
String.fromCharCode(parseInt(charCode, 16));

0xから始まる16進数の文字コードを表す文字列をparseInt()に与えて10進数の文字コード数値に変換し,更にそれをString.fromCharCode()に食わせる事で,所望の文字を得ることが出来るという感じです.


[追記] parseInt()の基底を16にしておけば"0x"をプレフィックスで付ける必要は無かった.

var charCode = "a9"; // <= これは文字列
String.fromCharCode(parseInt(charCode, 16));

16進数の文字コードを表す文字列 ("00"〜"ff") を基底16進のparseInt()に与えて10進数の文字コード数値に変換し,更にそれをString.fromCharCode()に食わせる事で,所望の文字を得ることが出来るという感じです.


無駄に詰まったのでここに記す.