夏休みの宿題でRubyPicoを再開

思ったことを最速でブログに書く環境もだいぶ整備できたのでRubyPicoの開発に戻る。まずは夏休みを使ってmain無しで動く最新のRubyPicoをリリースしたい。そのあとで書きかけのスマホではじめるRubyプログラミングを書く。プログラミングを学びたい人がRubyだけで色々なものを作れるようにしていく予定。

雑記: 洋書の読み方

Create Your Own Programming Languageを紹介したときに洋書読むの大変そうという話をよく聞いたので自分が使った教材を紹介する記事を書き始めた、が以外と長くなっているのでもう一息。端的に言うと

  • 「英語じゃないと読めないけどどうしても読みたい文章」を頑張って見つける
  • 基本からわかる英語リーディング教本
  • English Grammar in Use Book
  • 英単語アプリ mikan

みたいな感じです。

自分でプログラム言語を書いてみたい人は「Create Your Own Programming Language」がおすすめ

読み終わった。たった100Pにプログラム言語を作るための基礎(字句解析、構文解析、ランタイム、インタプリタ仮想マシン、ネイティブコンパイルまで!)が一通り学べ、さらに本書で作った実際に動くプログラミング言語がついてくる。 $39.99 とちょっと高いがプログラム言語を作る勉強代だと考えれば最も安くそして早く(ドラゴンブックは1090P)学べるのではないだろうか。洋書なのが難点だが半分くらいはソースコードなので苦労しながらなんとかなりました。(日本語訳出てほしいなぁ)

書籍内で作る言語は2種類で

  • Awesome
    • Rubyの構文にPythonのインデントブロックを混ぜ合わせたようなオブジェクト型
  • Mio
    • Ioを参考にしたメッセージ型

言語自体はどちらもRubyで書かれているが紹介される概念は特に言語の制約を受けないものが多い。

よかったところ

yaccやbison, JVM系の構文解析ツールなど実際に言語開発者によく使われているツール群の紹介が前半にたくさんあったのがよかった。この手のツールはあまりインターネットを漫然とサーフィンしているだけだとなかなか知る機会がないため。

「6. Runtime Model」の章。ものすごい数のプログラミング言語があるけどランタイムモデルの数はそこまで多くないということが分かった。これらと字句解析、構文解析の組み合わせで様々な言語が生まれる。(もちろん実際の言語はもう少し複雑でプロトタイプ型の特徴をもったクラス型、みたいな感じにいいとこどりだったりすると思うけど。)

  1. Procedural (手続き型、Cとか)
  2. Class-based (クラス型、Ruby, Python, C++, Javaとか、今だと一番多い?)
  3. Prototype-based (プロトタイプ型、JavaScript、実装がとてもシンプルに書けるのが特徴)
  4. Functional (関数型、Haskell, Lisp)

プログラム言語ってそれぞれ全然違うように見えて内部の実装の基礎的なところはかなり同じ概念で作られていることが多いので、プログラム言語を作ることに興味がない人でも、どんな基礎概念をベースに作られているのか、これらをたたき台に次はどんな言語が生まれるのか?と言ったことが分かるようになるのでおすすめです。

おまけ

こんなところにもmatzパワーが。

f:id:tuto0621:20160804002429p:plain

さらにこの本を読んで感動した Jeremy Ashkenas さんが書籍内のサンプル言語をたたき台に作った言語が CoffeeScript だというのだからすごい。(たしかに Awesome と CoffeeScript はちょっと似ているところがある。)

追記

書籍内では字句解析(lex)の部分はRuby正規表現を使って自前で作成、構文解析(yacc)の部分はRacc(Rubyで書かれたyacc)を使ってやっています。ツールのインストールにつまずきにくいのでよいですね。

この本を購入するには、著者のページのあのフォームにクレジットカード番号を入力して購入するしかないでしょうか? (少々、不安なフォームに見えるので・・・)

PayPalのアカウント作ってそこから購入するのがおすすめです。海外で買い物するならPayPalは作っておいて損はないと思います。

ファイル内のidの有無で新規か更新かを自動で判別できるように

ちょっとずつ自分好みの改善を加えていく。

使い方

新規作成

$ hw 001.md
http://ongaeshi.hatenablog.com/entry/2016/08/03/002228 (New)

更新

$ hw 001.md
http://ongaeshi.hatenablog.com/entry/2016/08/03/002228

記事を書き換えていくだけなら特にオプションも気にせずに実行すればよいということです。

これももう少し使ってみてよさそうだったらMRに出してみようかな。

hatenablog gemでタグ付きの文章が出力できない問題を解決した

色々とコード書き換えながら試したいのでローカルにbundle installする。

$ bundle install --path vendor/bundle

これでvendor/bundle以下にあるgemファイルを書き換えることでライブラリの挙動を変えたりpしながら調べることができる。

コードを読んでいくとxmlにエスケープが入っていないことが分かった。rubyでシンプルなXML escape。なので以下のようにエンコード関数を渡すことで動くようになった。

def entry_xml(title = '', content = '', categories = [], draft = 'no', updated = '', author_name = @user_id)
  .
  .
  xml % [e(title), author_name, e(content), updated, categories_tag, draft]
end

足したe()関数はこんな感じ。

def e(str)
  str.encode(xml: :text)
end

うまく行ったぜー、とプルリクエスト送ろうと思ったらkymmt90/hatenablogにあるコードと中身が違う、あれ?よくみるとbundle installで入っているのはhatenablog-0.2.1だけど最新はhatenablog-0.2.2であることに気がついた。これはもしかして・・・結局マージリクエストは以下のようになった。

Update hatenablog to 0.2.2 by ongaeshi · Pull Request #2 · kymmt90/hatenablog-writer

これで以下のようなタグ付きのテキストも問題なく投稿できるようになった。

<html>
</html>

hatenablog gemでタグ付きの文章が出力できない問題を解決した

色々とコード書き換えながら試したいのでローカルにbundle installする。

$ bundle install --path vendor/bundle

これでvendor/bundle以下にあるgemファイルを書き換えることでライブラリの挙動を変えたりpしながら調べることができる。

コードを読んでいくとxmlにエスケープが入っていないことが分かった。rubyでシンプルなXML escape。なので以下のようにエンコード関数を渡すことで動くようになった。

    def entry_xml(title = '', content = '', categories = [], draft = 'no', updated = '', author_name = @user_id)
      .
      .
      xml % [e(title), author_name, e(content), updated, categories_tag, draft]
    end

足したe()関数はこんな感じ。

    def e(str)
      str.encode(xml: :text)
    end

うまく行ったぜー、とプルリクエスト送ろうと思ったらkymmt90/hatenablogにあるコードと中身が違う、あれ?よくみるとbundle installで入っているのはhatenablog-0.2.1だけど最新はhatenablog-0.2.2であることに気がついた。これはもしかして・・・結局マージリクエストは以下のようになった。

Update hatenablog to 0.2.2 by ongaeshi · Pull Request #2 · kymmt90/hatenablog-writer

これで以下のようにタグ付きの文章でも問題なく投稿できるようになった。 :smile:

<html>
</html>

はてなブログをコマンドラインから投稿できるようにした

この記事がすでにコマンドラインで投稿している。 hateblog-writeというのをインストールしてみた。

インストール

はてなブログ API 用の gem を書いた - blog.kymmt.comに沿ってコンシューマキー、アクセストークンの順に取得、config.ymlを生成すればよい。

落とし穴1 権限

read_private, write_private はデフォルトOFFなので注意。全ての権限を有効にしておくこと。

$ get_access_token CONSUMER_KEY CONSUMER_SECRET
/opt/local/lib/ruby2.3/gems/2.3.0/gems/oauth-0.4.7/lib/oauth/consumer.rb:178:in `request': parameter_rejected (OAuth::Problem)
    from /opt/local/lib/ruby2.3/gems/2.3.0/gems/oauth-0.4.7/lib/oauth/consumer.rb:194:in `token_request'
    from /opt/local/lib/ruby2.3/gems/2.3.0/gems/oauth-0.4.7/lib/oauth/consumer.rb:136:in `get_request_token'
    from /opt/local/lib/ruby2.3/gems/2.3.0/gems/hatenablog-0.2.2/exe/get_access_token:26:in `get_request_token'
    from /opt/local/lib/ruby2.3/gems/2.3.0/gems/hatenablog-0.2.2/exe/get_access_token:50:in `get_access_token'
    from /opt/local/lib/ruby2.3/gems/2.3.0/gems/hatenablog-0.2.2/exe/get_access_token:60:in `<top (required)>'
    from /opt/local/bin/get_access_token:23:in `load'
    from /opt/local/bin/get_access_token:23:in `<main>'

落とし穴2 config.yml

うまくいかないときは大抵の場合config.ymlの設定をミスっている。はてなブログIDはドメインを指定する。私だったらongaeshi.hatenablog.comとか。

consumer_key: <コンシューマキー>
consumer_secret: <コンシューマシークレット>
access_token: <アクセストークン>
access_token_secret: <アクセストークンシークレット>
user_id: <ユーザ ID>
blog_id: <はてなブログ ID>
.
.
.
blog_id: ongaeshi.hatenablog.com

落とし穴3 ロケール

ロケールを設定しておかないと日本語を含むマークダウンを投稿できないので注意。以下を~/.profile~/.bashrcに設定。

export LC_ALL=ja_JP.UTF-8
export LANG=ja_JP.UTF-8

使い方

$ cd ~/Documents/hatenablog-writer
$ cat blog/001/md
Test

This is test

投稿

$ bundle exec hw -c diary blog/001.md

更新

$ bundle exec hw -u blog/001.md

改造

hatenablog-writer/hw に以下のコードを足すと更新後に記事のURLを出力してくれて幸せになれます。

HBWriter.new do |hb_writer|
  ARGV.each do |file_name|
    entry_text = ''
    open(file_name, 'r') do |f|
      if OPTS[:d]
        entry_text = hb_writer.delete_entry(f.read)
      elsif OPTS[:u]
-        hb_writer.update_entry(f.read, CATEGORIES)
+        e = hb_writer.update_entry(f.read, CATEGORIES)
+        puts e.uri
      elsif OPTS[:m]
        hb_writer.minor_update_entry(f.read, CATEGORIES)
      else
        entry_text = hb_writer.post_entry(f.read, CATEGORIES)
      end
    end
$ bundle exec hw -u blog/001.md
http://ongaeshi.hatenablog.com/entry/hatenablog-writer

バグ報告

動かないパターンを見つけたのでIssueに登録した。

contentに'<'が含まれるとエラー · Issue #1 · kymmt90/hatenablog-writer

RubyPico開発日記2 - SimpleHttpServerを2回起動するとserver.rb:50 bind (RuntimeError)

こんな感じ。

f:id:tuto0621:20160731154021p:plain

おそらく一度ソケットで確保したアドレスがプログラム終了後にcloseされずに確保しっぱなしになっているようだ。RubyPico自体を終了させると解放される。

CRubyはプロセスとして起動する場合が多いのでWebサーバーを止めたいときはそのプロセスを停止してしまえばよい、OSが安全に後始末してくれる。ところがiOSのアプリは子プロセスが作れない(forkできない)ので、RubyPicoは別スレッドでmrubyを動かしているのでプログラム終了時にこちらでなんとかしないといけない。プログラム終了後にはmrb_close()が呼ばれるのでそのタイミングで未解放のソケットがあればcloseする、例えばGC時に後始末関数を呼ぶような仕組みがいいのだろうか。

RubyにはFinalizerがあったがgrepした感じmrubyにはない。なのでRubyPicoのImageクラスのようにcloseされていなかったら解放時にcloseを呼ぶようなライブラリを用意するのがいいのかしら?

もしくはiOS用に用意されたCocoaHTTPServerというのがあるのでこいつをRubyPico用にバインドするのもいいかもしれない。でも、低レイヤのソケットライブラリが用意されているとどうやってHTTPが動いているのかを勉強するのにいいんだよなぁ、実際mruby-simplehttpserverはたった180行のRubyスクリプト書かれていて、私自身Webサーバがどうやって動いているのか理解するのにとても役立った。runとか関数の内容をRubyPico上でオーバーライドすれば簡単に挙動を変えて確かめられるしね。(ここがRubyのすごいところだと思う)