createObjectURLの発行したURLが無効になった時

って仕様上は500番ですよね?

Google Chromeは404(Not Found)を返すような


最近コイツのurlが殺されない方法を考えてる

background_pageにcreateObjectURLが発行したURLを送ってやれば,

セッションを閉じてもbg-pageが生きてるからURLも生き続けるよね的な

まぁbg-pageが死んだ瞬間パーだけど

Chromeを再起動したらbg-page死ぬんだよなぁ...うーむ...




そう言えばchrome.extension.sendRequestでBlobやFileを送ってもObjectになっちゃうんだよ

知ってるか? JavaScriptでココらへん触るとtype errorが返って来るんだぜ?

FileAPIあたりのセキュリティー相当キツイ



あ, そうそう Filesystem APIは意地でも使わないよ

ファイルがコピーされるなんてユーザーの意図に反するからね


追記: あれ, background-pageってChromeを落としても生き続けられるかも

もいっちぃ追記: そう言えばシークレットページの許可したら, なんでbackground-pageってシークレットページと2つ開くの?

Local Playerがビデオ対応しました!!!

Local Playerがビデオに対応しました!!!


ローカルのディスクにある動画(mp4)を再生できるようになりました!!!

createObjectURLを使えばいいということに昨日気がついて, これだけはアップしておこうと思ってリリースしました.

もう再生ソフトなんていらない!!! ブラウザーで十分だ!!!

追記(2012/11/21)

Local Playerはmanifest_version: 2の波についていけなくてStoreから削除されました.

createObjectURLがすごい件

最近またLocal Player (Chrome Player)を実装しなおしています.

一応説明しておくと, Local Playerは, 完全にローカルで動作する音楽プレイヤーです.

シンプルさを求め(実装がめんどくさいだけ), 操作しやすく(これは大事), 良い感じのプレイヤーです.

半年前に, ソースコードがスパゲッティになって, 開発を中断していましたが, 最近また書きなおし始めたのです.



音楽プレイヤーをブラウザー上で実装するのには, 音楽ファイルをJavaScriptで読み込まなければなりません.

そこでHTML5ですよ!!!

<audio src="url/to/musicfile.mp3" type="audio/mp3" />

みたいな感じで, 音楽を再生できます.

詳細は他のページに譲ります.



さて, 音楽ファイルを再生するには, ローカルファイルからurlをaudio要素のsrcにファイルをぶち込まなければならないわけですが, どうすればいいでしょう.

まぁ素直な実装は, File APIFileReaderreadAsDataURLを使うことでしょう.

実際, Local Playerは今までこの方法をとっていました.

だいたいこんな感じの実装です

// file は<input>から入ってきたやつ
var reader = new FileReader();
reader.onerror = function(e) {
  console.dir(e);
};
reader.onload = function(e) {
  var audio = new Audio(e.target.result);
  audio.volume = 0.5;
  audio.addEventListener('ended', function() {
    delete audio;
    next_music();
  });
  audio.play();
};
reader.readAsDataURL(file);

極めて普通ですね.

教科書そのまんまって感じです.

deleteはいらないと思いますが, e.target.resultが馬鹿でかい文字列となっており, メモリーがやばいことになります.

この実装を用いたLocal Playerで, 音楽を再生して, 10秒再生したらすぐ次のファイルへとスキップする, という動作を繰り返した時, CPU使用率とメモリー使用率は次のようになります.

確認環境はUbuntu, メモリーは2GBです.

曲を開始した瞬間, 5MBの音楽ファイルをreadAsDataURLして, メモリーが一時的に上がります.

大体, 山の大きさがメモリーの5%くらい, つまり, およそ100MBの文字列がJavaScriptの中でできては消え... を繰り返してるのです.

こんなんだと, 1秒ごとにスキップして行ったらどんどんメモリーは食うはCPUはしんどいわ...

しかも, 「曲をスキップする」というのは音楽プレイヤーとしては, よくあることなのです.

で, 一年間ほど悩んでいました.



今日, 神様のお告げが

ξ*‘ ー‘)<( それcreateObjectURLでデキルヨ )


え...???

どう違うの???

取り敢えず実装してみよう...

var createObjectURL 
  = window.URL && window.URL.createObjectURL
    ? function(file) { return window.URL.createObjectURL(file); }
    : window.webkitURL && window.webkitURL.createObjectURL
      ? function(file) { return window.webkitURL.createObjectURL(file); }
      : undefined;
if (!createObjectURL) return;
var audio = new Audio(createObjectURL(file));
audio.volume = 0.5;
audio.addEventListener('ended', function() {
  delete audio;
  next_music();
});
audio.play();

だいたいこんな感じの実装

はい, とってもとーっても簡単ですね (にこにこ

取り敢えずさっきと同じ条件で, 音楽を読み込んで次々とスキップしてみます


.......

いやいやいやいや...

モリー...一定じゃん...

これ, ブログ見てる人には分からないでしょうが, ちゃんと音楽も聞こえてますし, スキップしてますし...



分かりやすいように, 音楽を再生する瞬間にCPUにワザと負荷を与えるような感じのコードを入れてみます.

for (var i = 0; i < 2e4; i++) { 
  console.log(i);
};

これで, さっきと同じ条件で行ってみます.

ちゃんと音楽流れてるんですよ? ホントに. ホントです.

モリー全然食ってないじゃん...\ヤベェ/

まとめ

createObjectURLは凄い*1

何が凄いって, 全然ふっつーの関数呼び出しで書いてるのに, 裏ではurl先のローカルファイルをstream(?)的に読み込んでる.

あ, いや, 読み込んでるのはaudio elementの方なのかな...

これまでは, readAsDataURLで一気に読み込んでいたため, 50MBある1時間超のファイルを再生できませんでした.

それがcreateObjectURLによって解決されてしまったのです.

さらに, これまでできなかった動画再生も可能になります!!! ← なりました!!!

createObjectURL, どういう実装になってるんでしょう?

ちゃんと音楽や動画のシークもできるし, すごく不思議です.

不思議なのは, USB接続の外付けHDDなどにある音楽を, 再生して直ぐにUSBをぶっこぬいても, そのまま音楽が聞こえ続けることなんです.



フッ, またV8の闇に取り込まれそうになってしまったぜ...

追記(2012/11/21)

Local Playerはmanifest_version: 2の波についていけなくてStoreから削除されました.

*1:どうしよう, 凄いって思った次の日には, このURLが一時的なものだということに気がついて萎えた. みんなココで困ってる. 例えばHow to save the window.URL.createObjectURL() result for future use? createObjectURLに関するセキュリティとか勉強したい

Jison入門!!!

最近, Jisonを使おうとしている人をちらほら見るので, ちょっと入門的なやつを書いてみる.


定義(てけとー

  • Jisonというのは, JavaScriptで書かれたパーサーを吐くことができる, パーサジェネレーターの一つ


現在Jisonは https://github.com/zaach/jison で入手可能です.

色々説明したいけど, 構文解析とかLALRとか説明しだしたら, ボロが出る難しいので, ...

取り敢えずJisonのexampleを試してみよう!!!

まずは, narwhalをインストールしましょう

https://github.com/280north/narwhal
から落として, PATHを通す.
narwhalと叩いてJavaScriptインタラクティブな環境が立ち上がったら多分OK.
(というかnarwhalの詳しいことは僕はよく分からない...)


Jisonのgitリポジトリをローカルにcloneしよう!

$ git clone git://github.com/zaach/jison.git
$ cd jison/

(git入れてなくてもzipで落として解凍すれば使えるよ)

exampleフォルダがあるので, 早速, 試してみよう.

$ cd example/
$ cat calculator.jison

/* description: Parses end executes mathematical expressions. */

/* lexical grammar */
%lex
%%

\s+                   /* skip whitespace */
[0-9]+("."[0-9]+)?\b  return 'NUMBER'
"*"                   return '*'
"/"                   return '/'
"-"                   return '-'
"+"                   return '+'
"^"                   return '^'
"!"                   return '!'
"%"                   return '%'
"("                   return '('
")"                   return ')'
"PI"                  return 'PI'
"E"                   return 'E'
<<EOF>>               return 'EOF'
.                     return 'INVALID'

/lex

/* operator associations and precedence */

%left '+' '-'
%left '*' '/'
%left '^'
%right '!'
%right '%'
%left UMINUS

%start expressions

%% /* language grammar */

expressions
    : e EOF
        { typeof console !== 'undefined' ? console.log($1) : print($1);
          return $1; }
    ;

e
    : e '+' e
        {$$ = $1+$3;}
    | e '-' e
        {$$ = $1-$3;}
    | e '*' e
        {$$ = $1*$3;}
    | e '/' e
        {$$ = $1/$3;}
    | e '^' e
        {$$ = Math.pow($1, $3);}
    | e '!'
        {{
          $$ = (function fact (n) { return n==0 ? 1 : fact(n-1) * n })($1);
        }}
    | e '%'
        {$$ = $1/100;}
    | '-' e %prec UMINUS
        {$$ = -$2;}
    | '(' e ')'
        {$$ = $2;}
    | NUMBER
        {$$ = Number(yytext);}
    | E
        {$$ = Math.E;}
    | PI
        {$$ = Math.PI;}
    ;


$ narwhal ../bin/jison ./calculator.jison

jisonに, calculator.jisonファイルを渡してあげると, calculator.jsファイルが同じディレクトリに出来ます


calculator.js , できたかな?

$ cat calculator.js
/* Jison generated parser */
var calculator = (function(){
var parser = {trace: 
function trace() {
}
,
yy: {},
#####   省略   #####

$ narwhal calculator.js
Error: Usage: calculator.js FILE

ああ, ファイルを指定するのか...

$ echo '100 * (1 + 2)' > caltest.txt
$ narwhal calculator.js caltest.txt
300
$ echo '10!' > caltest.txt
$ narwhal calculator.js caltest.txt
3628800

(∩´∀`)∩ワーイ
取り敢えず, これ (calculator.jison) が動いたら, Jisonが使えるんじゃないかって思えてくる, よね!!!

ん, まぁコンソールでは動く事, 分かったけど...


Jisonの吐いたJavaScriptファイルをHTMLで使いたいんだけど

大丈夫!!!

ちょっとテストを書いてみましょう

先ほどのcalculator.jsと同じところにcalculator.htmlとして次のように書いてみます

<!DOCTYPE HTML> 
<html lang="en"> 
<head> 
  <meta charset="UTF-8"> 
  <title>jisonによる計算機</title> 
  <script src="./calculator.js" type="text/javascript"></script> 
  <script type="text/javascript"> 
    onload = function () {
      var exprTextArea = document.getElementById('expression'),
          resultTextArea = document.getElementById('result'),
          expr,
          ans;
      exprTextArea.addEventListener('keydown', function () {
        setTimeout(function () {
          expr = exprTextArea.value;
          try {
            ans = calculator.parse(expr);          //  ←←  ここ重要だから覚えておきな!!!!
            resultTextArea.innerText = ans;
          } catch (e) {
            resultTextArea.innerText = e;
          }
        }, 20);
      });
    }
  </script> 
</head> 
<body> 
  <textarea id="expression" rows="20" cols="50"></textarea> 
  <textarea id="result" rows="20" cols="50"></textarea> 
  <br /> 
  数式を入れてください
</body> 
</html> 

ブラウザーで開いてみて, 左のtextareaに書いた式の結果がちゃんと計算できてたらOK.

fc2の方にもサンプルをアップしておくよ!
http://itchyny.web.fc2.com/calculator/

わーい, たのしぃーー!!!

構文解析は???

計算機ができたのはいいけど, 計算木も作りたいよね ← !!!

ってことで, こういうふうに書いてみるよ!

$ cat calculator2.jison

%lex
%%

\s+                   /* skip whitespace */
[0-9]+("."[0-9]+)?\b  return 'NUMBER'
"*"                   return '*'
"/"                   return '/'
"-"                   return '-'
"+"                   return '+'
"^"                   return '^'
"!"                   return '!'
"%"                   return '%'
"("                   return '('
")"                   return ')'
"PI"                  return 'PI'
"E"                   return 'E'
<<EOF>>               return 'EOF'
.                     return 'INVALID'

/lex

/* operator associations and precedence */

%left '+' '-'
%left '*' '/'
%left '^'
%right '!'
%right '%'
%left UMINUS

%start expressions

%% /* language grammar */

expressions
    : e EOF { return $1; }
    ;

e
    : e '+' e            {$$ = new Add($1, $3);}
    | e '-' e            {$$ = new Sub($1, $3);}
    | e '*' e            {$$ = new Mul($1, $3);}
    | e '/' e            {$$ = new Div($1, $3);}
    | e '^' e            {$$ = new Pow($1, $3);}
    | e '!'              {$$ = new Factorial($1);}
    | e '%'              {$$ = new Percentage($1);}
    | '-' e %prec UMINUS {$$ = new Minus($2);}
    | '(' e ')'          {$$ = $2;}
    | NUMBER             {$$ = Number ($1);}
    | E                  {$$ = E;}
    | PI                 {$$ = PI;}
    ;

$ narwhal ../bin/jison/ ./calculator2.jison
$ 

すぐに計算しちゃわずに, オブジェクトを作るんだよ!
例えば,

function Add (x, y) { this.left = x; this.right = y; }

みたいに定義しておくと, 左辺と右辺が入るよ!

で, オブジェクトが返ってくるから, 再帰でぐるぐる回ってみたらいいんじゃないかな!!
ってことで, サンプルはこちら↓
http://itchyny.web.fc2.com/calculator2/



ちょっと説明を加えておくよ

  1. hoge.jisonをjisonで処理すると, hoge.jsファイルができるよ
  2. その中の, hoge.parse関数を使うよ
  3. jisonファイルに書く, %startに注意. パースはここから始まるよ
  4. "{ $$ = ... ;}" を省略すると, $1の値がフォールするよ (多分
  5. トークンを読み込むための「正規表現」が特徴ある形だから, サンプルを参考にしたほうがいいよ

jsparser

JisonはCoffeeScriptの処理系にも使われてるくらいだから, 色々できるんだよ!!!

自分がJisonで, (二ヶ月ほど前に) 作ったものは, これ↓
http://itchyny.web.fc2.com/jsparser/

JavaScriptのパーサーも作れちゃうんだね!!! *1

みんなもJisonで遊んでみよう!!!

楽しいですよ!!! よ! よ! >ω<ノシ

*1:実は, 正規表現周りでサボっているところがあって, var x = 1 / 2 / 3; みたいな式が処理できませんorz. でも, セミコロン挿入の処理は割と頑張ったから, それで燃え尽きた.

CoffeeScriptをやってみた

ずっと避けてきたCoffeeScriptを使ってみた


HTMLはJade,

CSSLESS,

JavaScriptCoffeeScript

がいいんじゃないかな?



インストールとかは公式に載ってるから割愛

三つともVimのsyntaxが既に作られているのは嬉しい



テキトーに書いたサンプルみたいな物

Jade

LESS

CoffeeScript


さてさて, CoffeeScriptですが, この文法がすごく酷い素晴らしい!!!

Code
  : "(" ParamList ")" FuncGlyph Block
  | FuncGlyph Block
;

FuncGlyph
  : "->"
  | "=>"
;

Codeってのは関数リテラルのことね (微妙なネーミング...

あ, 文法とかはgrammar.coffeeとか見れば載ってる


例えば

f = (x) -> x * x

をcoffeeでコンパイルすると

f = function(x) {
  return x * x;
};

となる

カーリー化も簡単に書けるヨ *1

f = (x) -> (y) -> x * x + y
f = function(x) {
  return function(y) {
    return x * x + y;
  };
};


さて, ここまでは別にいいのだが, 括弧を書かなくても良いことから, ひょんな事で別の意味になる

foo bar -> baz
foo bar, -> baz
foo bar: -> baz

順番に

foo(bar(function() {
  return baz;
}));
foo(bar, function() {
  return baz;
});
foo({
  bar: function() {
    return baz;
  }
});

である
ウギギ...分かりにくっ


顔文字!!!!
(JavaScriptとしては実行できないが)

\(^o^)/ = (´・ω・`) -> ☆(ゝω・)vキャピ
\(^o^)/ = function(´・ω・`) {
  return ☆(ゝω・)vキャピ;
};

で, 今回のオチは

λ = (x) -> (s) -> (eval '(' + x + ')') s

x = (f) -> f.toString().replace /\( *\)/, '(x)'

alert (λ x -> 2 * x) 150               # 300

ラムダ式も書けちゃうなんて...!!!
CoffeeScriptまじλちゃん


結論

確かに素晴らしいが, 今後の文法の変動に注意

*1:ただ, f 100 200 がf( 100(200) )となってしまう