ひがきの日記

2016-02-07

C++ で Ruby の inject みたいなやつを作ってみる

仕事で C++ を使うことになったので、リハビリを兼て Ruby の inject みたいな (lisp の apply みたいな) 関数を書いてみた。

#include <iostream>
#include <vector>
#include <functional>

using namespace std;

#define ARRSIZ(a) (sizeof(a)/sizeof((a)[0]))
#define ARREND(a) ((a)+ARRSIZ(a))

template <class T, class R, class F>
R inject(T begin, T end, R result, F func) {
    for (; begin != end; ++begin)
	result = func(result, *begin);
    return result;
}

template <class T>
struct PLUS {
    T operator()(const T& a, const T& b) const {
        return a + b;
    }
};

int main() {
    int src[] = {1, 2, 3, 4, 5};
    vector<int> v;
    vector<double> w;

    for (int* i = src; i != ARREND(src); ++i) {
	v.push_back(*i);
	w.push_back(*i * 1.1);
    }

    cout << inject(src, ARREND(src),   0,   plus<int>())    << endl;
    cout << inject(v.begin(), v.end(), 0,   plus<int>())    << endl;
    cout << inject(w.begin(), w.end(), 0.0, plus<double>()) << endl;
    cout << inject(v.begin(), v.end(), 0,   PLUS<int>())    << endl;
    cout << inject(w.begin(), w.end(), 0.0, PLUS<double>()) << endl;

    return 0;
}

inject の受け取る、型 F がなんかよく分からん。

template 使うと、関数オブジェクト関数内でちょいちょいっと書けないのかね。

    struct _plus_ {
	int operator()(const int& a, const int& b) const {
	    return a + b;
	}
    };
    cout << inject(v.begin(), v.end(), 0, _plus_()) << endl;

上のように main() の中で struct を書くと、

warning: template argument uses local type '_plus_'

こんな感じで怒られる。めんどくさい。

今時ならラムダ式を使うといいんだろうか?

そもそも inject に相当する関数はどこかに定義されてるんじゃないのか?

2013-04-13

Ruby初級者向けレッスン 45回 ─例外─

第57回 Ruby/Rails勉強会@関西での初級者向けレッスンの資料を公開します。*1

f:id:mas-higa:20130416224751p:image:w360

エラーメッセージ

require 'open-uri'
open 'http://github.com/rubykansai/workshops/wiki'
.../open-uri.rb:223:in `open_loop': redirection forbidden: http://github.com/rubykansai/workshops/wiki -> https://github.com/rubykansai/workshops/wiki (RuntimeError)
        from .../open-uri.rb:149:in `open_uri'
        from .../open-uri.rb:688:in `open'
        from .../open-uri.rb:34:in `open'
        from ex.rb:2:in `<main>'

f:id:mas-higa:20130416224727p:image:w360

  • 「...(RuntimeError)」までが 1行
  • 後は「from ...」から行末までが 1行
  • 最初の行がエラーの出た場所。
    • 「in `open_loop'」は open_loop メソッドでエラーが出たという意味。
    • 「.../open-uri.rb:223」は open-uri.rb の 223行目でエラーが出たという意味。
      • えっ、でも open_loop なんて見覚えないよ!?
  • 最後の行が呼び出し元。
  • 下から 2行目「in `open'」は <main> から open メソッドが呼ばれたという意味。
  • 結局、英語のメッセージを読む必要がある ><
    • 「redirection forbidden」理由は分からんが http から https へのリダイレクトが禁止されてるらしい。
  • 最初の行の行末「(RuntimeError)」は RuntimeError 例外が発生したという意味。

エラーメッセージと例外

f:id:mas-higa:20130416224703p:image:w360

  • 例外が起きたら、なんらかの対処をしないと、プログラムが異常終了する。

いろいろな例外と、そのエラーメッセージを見て、例外に慣れよう。

例外いろいろ TypeError

"1" + 1

# ~> ex.rb:1:in `+': can't convert Fixnum into String (TypeError)
# ~> 	from ex.rb:1:in `<main>'

f:id:mas-higa:20130416224639p:image:w360

例外いろいろ NoMethodError

1.to_sym

# ~> ex.rb:1:in `<main>': undefined method `to_sym' for 1:Fixnum (NoMethodError)

f:id:mas-higa:20130416224610p:image:w360

例外いろいろ NameError

n.times{puts 'Ruby!'}

# ~> ex.rb:1:in `<main>': undefined local variable or method `n' for main:Object (NameError)

f:id:mas-higa:20130416224539p:image:w360

例外いろいろ NoMethodError (再び)

n = ARGV.first.to_i unless ARGV.empty?
n.times{puts 'Ruby!'}

# ~> ex.rb:2:in `<main>': undefined method `times' for nil:NilClass (NoMethodError)

f:id:mas-higa:20130416224513p:image:w360

  • Rubyには、他の言語のように NullPointerException のようなものはない。

例外いろいろ Errno::ENOENT

open('nothing.txt')

# ~> ex.rb:1:in `initialize': No such file or directory - nothing.txt (Errno::ENOENT)
# ~> 	from ex.rb:1:in `open'
# ~> 	from ex.rb:1:in `<main>'

f:id:mas-higa:20130416224446p:image:w360

  • 存在しないファイルを open しようとした。
  • 「in `initialize'」は File.open メソッドが Fileクラスのインスタンスを生成しようとしている。
  • Errno::Exxx の例外は OS の errno に対応した例外。

例外いろいろ SyntaxError

1 def even?(n)
2   if n % 2 == 0
3     true
4   else
5     false
6 end
7 
8 puts even?(0)
9 puts even?(1)

# ~> -:9: syntax error, unexpected end-of-input, expecting keyword_end

f:id:mas-higa:20130416224423p:image:w360

  • if-else-end の end を忘れている。
  • Ruby のパーサがスクリプトを最後まで読んだが、end が見つからなかった。
  • end を忘れたのは 5行目と 6行目の間だが、例外は最後で出るので注意。

Intelligence と Wisdom

雨が降ってきて…
  • Intelligence
    • 雨だ!
  • Wisdom
    • 傘を差そう
    • 雨宿りしよう

f:id:mas-higa:20130416224357p:image:w360

雨が降ってきたときに、

  • それが雨だと認識できる ─── Intelligence (知識)
  • 濡れないためには、傘を差すか、雨宿りしないと ─── Wisdom (知恵)

例外も同じで、

  • エラーを検知したものが例外を起こす (Intelligence)
  • 対処方法を知っているものが例外を捕捉する (Wisdom)

例えば、アプリケーションがファイルを読もうとして、

  • open できない!
  • read できない!

File ライブラリはエラーを検知できる。(Intelligence)

  • 既定値を使うから、読めなくてもよい。
  • 別のファイルから読みなおしたい。
  • etc...

これはアプリケーションの仕様。(Wisdom)
ライブラリには分かりようがない。

例外を捕捉する (コード例1)

files = %w[file.txt file1.txt file2.txt]

files.each do |fn|
  begin
    open(fn, 'w'){|f| f.puts "Ruby!!"}
    break
  rescue => ex
    $stderr.puts "#{ex} (#{ex.class})"
  end
end

f:id:mas-higa:20130416224328p:image:w360

やりたいこと

  • ファイルに "Ruby!!" と書き込みたい。
  • ファイル名は file.txt
    • もし書けなければ、代わりに file1.txt に書こう。
    • それでも書けなければ、file2.txt に書こう。
    • それでダメなら諦める。

コード解説

  • ファイル名 "file.txt", "file1.txt", "file2.txt" という Array を用意。
  • 各ファイル名に対して処理を繰り返す。─── files.each ...
    • begin - end で例外を捕捉する準備。
    • ファイルに "Ruby!!" を書き込む。─── open( ...
      • 例外が起きると、rescue節に処理が移る。
      • 変数 ex が例外オブジェクトを指す。
      • ここでは、標準エラー出力に例外情報を出力するだけ。
      • 最初の繰り返しに戻る。─── files.each ...
    • 次のファイル名で open & puts に再挑戦。
      • 例外が起きなければ break で繰り返しを脱出する。

やってみる

$ ls -Fdl file*
drwxr-xr-x  2 mas  staff  68  4 12 23:37 file.txt/
-r--r--r--  1 mas  staff   0  4 12 23:37 file1.txt
# file.txt はディレクトリ
# file1.txt は書き込み権なし

$ ruby retry.rb
Is a directory - file.txt (Errno::EISDIR)
Permission denied - file1.txt (Errno::EACCES)

$ ls -Fdl file*
drwxr-xr-x  2 mas  staff  68  4 12 23:37 file.txt/
-r--r--r--  1 mas  staff   0  4 12 23:37 file1.txt
-rw-r--r--  1 mas  staff   7  4 12 23:39 file2.txt
# file2.txt ができた!

$ cat file2.txt 
Ruby!!

例外を捕捉する

begin
  式1…
[rescue [型1[, 型2]…][=> 変数][then]
  式2…]…
[else
  式3…]
[ensure
  式4…]
end

f:id:mas-higa:20130416224258p:image:w360

  • [ ... ] 内は省略可能。
  • begin - end で例外を捕捉する準備。
  • 式1 が例外を捕捉したい処理 (複数書ける)
  • rescue節で例外を捕捉。(rescue は複数書ける)
    • 「型」に例外クラスを指定すると、特定の例外だけを捕捉できる。(型は複数書ける)
    • 式2 が例外対処のための処理 (複数書ける)
    • 発生した例外に対応する rescue節がなければ、捕捉できない。
  • ensure節は begin - end のブロックを抜ける際の後始末。
    • 例外が起きても、起きなくても実行される。
    • 式4 が後始末の処理 (複数書ける)
  • else節は、上記 式1 で例外が起きなかった際に実行される。
    • rescue節で指定しなかった例外を捕捉する処理ではない。
    • 式3 で起きた例外は、このブロックでは捕捉しない。

上記のコードを省略なしで書くと、3通りの実行パターンがある。

  • 式1→式3→式4 (例外なし)
  • 式1→式2→式4 (例外を捕捉)
  • 式1→式4 (例外が発生するが、捕捉できない)

例外を捕捉する (コード例2)

require './factorial'

def fact(n)
  n.factorial
rescue ArgumentError
  "1以上の整数を指定してください"
rescue NoMethodError
  "整数を指定してください"
end

fact 3      # => 6
fact 4      # => 24
fact 5      # => 120
fact 0      # => "1以上の整数を指定してください"
fact 2.5    # => "整数を指定してください"
fact "2"    # => "整数を指定してください"

f:id:mas-higa:20130416224157p:image:w360

  • メソッド定義の def - end にも rescue, else, ensure が書ける。*4

例外の種類

puts NoMethodError.ancestors
# >> NoMethodError
# >> NameError
# >> StandardError
# >> Exception
# >> Object
# >> Kernel
# >> BasicObject

f:id:mas-higa:20130416224130p:image:w360

rescue修飾子

require './factorial'

3.factorial   rescue 0  # => 6
4.factorial   rescue 0  # => 24
5.factorial   rescue 0  # => 120
0.factorial   rescue 0  # => 0
2.5.factorial rescue 0  # => 0
"2".factorial rescue 0  # => 0

f:id:mas-higa:20130416224057p:image:w360

  • rescue の後置。
    • まず rescue の左辺を評価。
    • 例外が起きると rescue の右辺を評価。
  • 例外が起きたら、結果は 0 でいい。という大雑把なコード。

例外を起こす

raise "simple"

# ~> ex.rb:1:in `<main>': simple (RuntimeError)

f:id:mas-higa:20130416224018p:image:w360

  • 例外を起こすには Kernel#raise メソッドを使う。
  • RuntimeError が発生する。

例外を起こす (クラス指定)

raise ArgumentError, "bad argument"

# ~> ex.rb:1:in `<main>': bad argument (ArgumentError)

f:id:mas-higa:20130416223946p:image:w360

  • 例外クラスを指定して raise できる。

例外を起こす (オブジェクト)

raise TypeError.new("can't convert...")

# ~> ex.rb:1:in `<main>': can't convert... (TypeError)

f:id:mas-higa:20130416223843p:image:w360

例外を起こす (独自の例外クラス)

class MyError < StandardError; end

raise MyError, 'original'

# ~> ex.rb:3:in `<main>': original (MyError)

f:id:mas-higa:20130416223758p:image:w360

  • 独自の例外クラスを作れる。*6

サンプルコード

スライド、コードに間違いがあれば、ご指摘ください。

*1:スライドだけ欲しい人は直接どうぞ http://higaki-it.jp/ruby/57/print.pdf

*2スタックトレースとかバックトレースとか言う。

*3:あれ? 今実行したらメッセージ違う!?

*4:else節の評価結果はメソッドの評価結果となるが、ensure節の評価結果はメソッドの評価結果とはならない。

*5:例外でないオブジェクトを指定すると TypeError が発生する。えっ?

*6エンタープライズだと独自の例外じゃないと起こせないことあるよね。

*7:StandardError なら、クラス指定なしの rescue でも捕捉できるから。

2013-02-02

Ruby初級者向けレッスン 44回 ― ブロック ―

第56回 Ruby/Rails勉強会@関西での初級者向けレッスンのスライドを公開します。*1

f:id:mas-higa:20130205231822p:image

繰り返し

a = [0, 1, 2]

a.each do |i|
  puts i
end

a.each{|i| puts i}

# >> 0
# >> 1
# >> 2

f:id:mas-higa:20130204232616p:image

  • ブロックの代表的な使い方は繰り返し処理。
  • do と end で囲まれたもの、{ と } で囲まれたものがブロック。
    • 上のコードと下のコードは同じ処理をしている。
  • Array のように、たくさんのオブジェクトを持っていると、全てのオブジェクトに繰り返し同じ処理をしたいことがよくある。
  • | と | で囲まれた変数 i が Array の各要素を順に指す。

便利な例

a = [0, 1, 2, 3]        # => [0, 1, 2, 3]

a.map{|i| i * i}        # => [0, 1, 4, 9]
a.select{|i| i.even?}   # => [0, 2]
a.inject{|s, i| s + i}  # => 6
a.find{|i| i.odd?}      # => 1
a.all?{|i| i.even?}     # => false
a.any?{|i| i.even?}     # => true

f:id:mas-higa:20130204232617p:image

  • 初級者には、このスライドがおすすめ。

f:id:mas-higa:20130204232620p:image


ブロックを渡す

  • メソッドには、ブロックをひとつ渡せる。
  • ブロックをどう使うかは、メソッド次第。
    • 繰り返し
    • ハリウッドの原理
open('hello.txt')   # => #<File:hello.txt>
open('hello.txt'){|f| f.read}
                    # => "こんにちは\n"

f:id:mas-higa:20130204232618p:image

  • ブロックの役割りは、繰り返しだけではない。
  • ブロックの有無で動作を変えるメソッドがある。

ハリウッドの原理

# open('hello.txt'){|f| f.read}

begin
  f = open('hello.txt')
  f.read
ensure
  f.close unless f.nil?
end

f:id:mas-higa:20130204232619p:image

  • 処理のスケルトンをメソッドで用意しておき、処理の一部をブロックで切り替える。
  • ブロック付きの 1行で書いた open メソッドは、ブロックなしで書くと、だいたいこんな感じ。
  • close の部分は f.close unless f.closed? かも。

ブロックのない open

f:id:mas-higa:20130206000309p:image

  • ファイルを扱う場合は、以下の処理が必要。
    1. open
    2. read/write など
    3. close
  • コードを書く際の負担が多い。

ブロック付き open

f:id:mas-higa:20130206000308p:image

  • open メソッドが忘れずに close してくれる。
  • コードを書く際の負担が少ない。

値を受け取る

f:id:mas-higa:20130204232621p:image


値を受け取る (2)

  • 受け取るか受け取らないかは、ブロック次第。
2.times{puts 'こんにちは'}
# >> こんにちは
# >> こんにちは

2.times{|i| puts i}
# >> 0
# >> 1

f:id:mas-higa:20130204232622p:image

  • 上の例は値を受け取っていない。
  • しかし times メソッドは値を渡してくれる。(下の例)
  • ブロックは必ずしも値を受け取る必要はない。

Hash の例

people = {matz: 47, dhh: 32}
            # => {:matz=>47, :dhh=>32}

people.each{|person| p person}
# >> [:matz, 47]
# >> [:dhh, 32]

f:id:mas-higa:20130204232623p:image

  • key と value のペアを受け取れる。
  • ふたつの値が、ひとつの Array オブジェクトに。

Hash の例 (2)

people = {matz: 47, dhh: 32}

people.each do |name, age|
  p "#{name}(#{age})"
end

# >> "matz(47)"
# >> "dhh(32)"

f:id:mas-higa:20130204232624p:image

  • ふたつの値を、それぞれ別の変数で受け取れる。
  • | と | の間に , で区切って変数を列挙する。

each_cons の例

midosuji = ["梅田", "淀屋橋", "本町", "心斎橋", "なんば"]

midosuji.each_cons(2){|path| p path}

# >> ["梅田", "淀屋橋"]
# >> ["淀屋橋", "本町"]
# >> ["本町", "心斎橋"]
# >> ["心斎橋", "なんば"]

f:id:mas-higa:20130204232625p:image


each_cons の例 (2)

midosuji.each_cons(2) do |from, to|
  p "#{from} - #{to}"
end

# >> "梅田 - 淀屋橋"
# >> "淀屋橋 - 本町"
# >> "本町 - 心斎橋"
# >> "心斎橋 - なんば"

f:id:mas-higa:20130204232626p:image

  • ふたつの値を、ふたつの変数で受け取る。

each_cons の例 (3)

a = [*0..3]     # => [0, 1, 2, 3]

a.each_cons(3){|i| p i}
# >> [0, 1, 2]
# >> [1, 2, 3]

a.each_cons(3){|i, j| p [i, j]}
# >> [0, 1]
# >> [1, 2]

f:id:mas-higa:20130204232627p:image

  • 繰り返しの回数は、どちらも 2回。
  • みっつの値を、ひとつの変数で受け取ると Array オブジェクトになる。(上)
  • みっつの値を、ふたつの変数で受け取ると、みっつ目の値が受け取れない。(下)

おかしいな? と思ったら

p unknowns.first
            # >> [1, ["matz", 47]]

unknowns.each do |id, (name, age)|
  id        # => 1
  name      # => "matz"
  age       # => 47
end

f:id:mas-higa:20130204232628p:image

  • 要素を、ひとつ取り出して見る。
  • 例えば、こんなふうに表示されたら、値は幾つか?
    • 答えは、ふたつ! *2
      1. 数値 (1)
      2. Array
        • 中身は、文字列 ("matz') と数値 (47)
  • みっつの変数で受け取るには Array の中の Array を ( ) で表記する。

ブロックを受け取るメソッド

  • こんな感じで呼びたい
monta{puts 'block!'}

# >> block!
# >> block!
# >> 大切なことなので

f:id:mas-higa:20130204232629p:image

  • monta というメソッドを作る。
    • ブロックを 2回評価して、
    • 最後に '大切なことなので' と出力する。

ブロックを受け取る方法は、ふたつある。


ブロックを受け取る

def monta
  yield
  yield
  puts '大切なことなので'
end

f:id:mas-higa:20130204232630p:image

  • yield メソッドで受け取ったブロックを評価する。

ブロックを受け取る (2)

def monta &block
  block.call
  block.call
  puts '大切なことなので'
end

f:id:mas-higa:20130204232631p:image

  • 引数でブロックを受け取る。
  • call メソッドでブロックを評価する。

値を渡す

  • monta メソッドの仕様を変更。
  • ブロックに '大切なことなので' という文字列を渡す。
def monta
  yield '大切なことなので'
  yield '大切なことなので'
end

monta{|i| puts "#{i} block!"}

# >> 大切なことなので block!
# >> 大切なことなので block!

f:id:mas-higa:20130204232632p:image

  • yield メソッドに文字列を渡すだけの簡単なお仕事です。

値を渡す (2)

  • また monta メソッドの仕様を変更。
  • ブロックに '大切な', 'ことなので' という、ふたつの文字列を渡す。
def monta &block
  block.call '大切な', 'ことなので'
  block.call ['大切な', 'ことなので']
end

monta{|i| puts "#{i} block!"}

# >> 大切な block!
# >> ["大切な", "ことなので"] block!

f:id:mas-higa:20130204232633p:image

  • block.call に、ふたつの文字列を渡すだけだと……
    • ブロックがひとつの変数で待ち受けていると、ふたつ目の文字列が受け取ってもらえない。
  • ふたつ以上の値を渡すときは、Array のオブジェクトにして渡す。

ブロックは Proc

block = Proc.new do |i, j|
  puts "#{i}#{j} block!"
end

monta &block

# >> 大切なことなので block!
# >> 大切なことなので block!

f:id:mas-higa:20130204232634p:image

  • あらかじめブロックだけ生成しておける。
  • メソッドに渡す際には & を付ける。

Q&A

Q1
ブロックをふたつ渡せないの?
A1
& なしで普通の引数としてなら渡せます。
Q2
さっきから Emacs で Ruby のコード実行してるけど、それなに?
A2
るびきちさんが作られた rcodetools を使ってます。Ruby を起動して実行結果を # => の後に埋め込んでくれます。(コード補完もできます) コメントなので、結果の埋め込まれたコードは、そのまま保存・実行できます。
Q3
どんなキーバインドなの?
A3
えっ、それ重要?

*1:スライドだけ欲しい人は直接どうぞ http://higaki-it.jp/ruby/56/slide.pdf

*2:こんなデータ構造が Array で渡ってきたら、データ構造の設計が間違ってる。

2010-01-22

IronRuby を使ってみた

仕事で C# を使うことになったので「Ruby on Windows」を見ながら RubyCLR を試してみた。

Rubyist Magazine出張版 Ruby on Windows

ところが Ruby 1.9.1 に対応していないらしく、require すると SyntaxError が出る。

case
when: ...

when の : をひたすら ; に置換するなどした。

それでも activesupport 絡みでエラーが出る*1ので諦めた。

そこで IronRuby ですよ

IronRubyは、.NET Framework上で動作する、マイクロソフトによるRubyの実装である。
(Wikipedia より)

http://ironruby.net/ からバイナリをダウンロードして適当なディレクトリに展開。

bin/ir.exe というのが本体らしい。

$ ir
/path/to/IronRuby/bin/ir: line 5: mono: command not found

なんじゃこりゃー。

$ cat /path/to/IronRuby/bin/ir
#!/usr/bin/env bash

fpath=`which $0`
fdir=`dirname $fpath`
mono $fdir/ir.exe $*

なにこれ、うける。

拡張子も指定して ir.exe を起動する。

$ ir.exe
IronRuby 0.9.3.0 on .NET 2.0.0.0
Copyright (c) Microsoft Corporation. All rights reserved.

>>>

MRI はスクリプト全体をパースしてから実行するけど、IronRuby はスクリプトを逐次読み込んで実行するらしい。

ir.exe に標準入力からコードを与えると対話的に実行してくれる。

>>>puts 'Hello IronRuby'
Hello IronRuby
=> nil

>>> require 'System'
=> true

>>> System::Console.WriteLine 'Hello .NET'
Hello .NET
=> nil

もうちょっと複雑なのも試してみる。

require 'System'
require 'System.Data'

con = System::Data::SqlClient::SqlConnectionStringBuilder.new
con.DataSource = '(local)'
con.UserID = 'scott'
con.Password = 'tiger'

db = System::Data::SqlClient::SqlConnection.new
db.ConnectionString = con.to_s
begin
  db.Open

  sql = db.CreateCommand
  sql.CommandText = "SELECT 'Hello SQL' FROM DUAL"
  sql.ExecuteReader.each do |rec|
    System::Console.WriteLine rec[0]
  end
ensure
  db.Close
end

#> Hello SQL

う〜ん、気持悪い。
もうちょっと Ruby らしく書けないもんだろうか。

例えば IDisposable なクラスなら C# で using が使えるように、ブロック付きメソッドで自動解放してくれるとか。

なんか .NET のクラスをそのまま見せてくれてるだけで、ちっとも嬉しくない。
Ruby っぽいラッパークラスとかないのかなぁ。

String (Ruby) と System::String (.NET) さえも区別してる。

>>> con.to_s
=> "Data Source=(local);User ID=scott;Password=tiger"

>>> con.ToString
=> 'Data Source=(local);User ID=scott;Password=tiger'

>>> con.to_s.class
=> String

>>> con.ToString.class
=> System::String

やっぱり Microsoft との間には埋められない溝があるのか知らん。

*1:MissingSourceFile って何だ?

2009-07-05

anything から refe.el を使う

Emacs から「るりま」を引くときは anything を使うのが普通らしい。

anything から refe.el を使えるようにしてみた。

refe.el を使う意味は?

特にない。
やってみたかったから。

refe.el の修正

refe に検索する単語を渡せるようにする。

(defvar refe-program-name
  (if (featurep 'meadow)
      "ruby"
    "refe"))
(defvar refe-program-args
  (if (featurep 'meadow)
      '("-S" "refe")
    nil))
(defvar refe-buffer-name "*Refe*")
(defvar refe-completion-table nil)

(defun refe-call-process (buf &rest args)
  (let ((coding-system-for-read 'euc-japan))
    (apply 'call-process refe-program-name nil buf nil
           (append refe-program-args args))))

(defun refe-make-completion-table ()
  (setq refe-completion-table (make-vector 547 0))
  (with-temp-buffer
    (refe-call-process t)
    (goto-char (point-min))
    (while (looking-at "[A-Z][A-Za-z_0-9:]*")
      (intern (match-string 0) refe-completion-table)
      (skip-chars-forward "^ \n")
      (skip-chars-forward " \n")))

  (with-temp-buffer
    (refe-call-process t "")
    (goto-char (point-min))
    (while (looking-at "\\(\\$[^ \n]*\\|[A-Z][A-Za-z_0-9:]*[#.][^ \n]*\\)")
      (intern (match-string 0) refe-completion-table)
      (skip-chars-forward "^ \n")
      (skip-chars-forward " \n")))
  nil)

(refe-make-completion-table)

(defun refe-get-word-at-point ()
  (save-excursion
    (while (looking-at "\\sw\\|\\s_")
      (forward-char 1))
    (if (or (re-search-backward "\\sw\\|\\s_"
				(save-excursion (beginning-of-line) (point))
				t)
	    (re-search-forward "\\(\\sw\\|\\s_\\)+"
			       (save-excursion (end-of-line) (point))
			       t))
	(progn (goto-char (match-end 0))
	       (buffer-substring (point)
				 (progn (forward-sexp -1)
					(while (looking-at "\\s'")
					  (forward-char 1))
					(point))))
      nil)))

(defun refe (&optional word)
  (interactive)
  (if (null refe-completion-table)
      (refe-make-completion-table))
  (let* ((default (refe-get-word-at-point))
	 (completion-ignore-case t)
	 (buf (or (get-buffer refe-buffer-name)
		  (generate-new-buffer refe-buffer-name)))
	 (pop-up-windows t)
	 (pop-up-frames nil))

    (if (null word)
	(setq word (completing-read
		    (if default
			(format "Class or Method (default %s): " default)
		      "Class or Method: ")
		    refe-completion-table nil nil nil nil default)))

    (set-buffer buf)
    (setq buffer-read-only nil)
    (erase-buffer)

    (refe-call-process buf word)

    (goto-char (point-min))
    (if (re-search-forward "^---" nil t)
	nil
      (let ((klass-table (make-vector 17 0))
	    (completion-ignore-case t)
	    klass)
	(goto-char (point-min))
	(while (looking-at "\\([A-Z][A-Za-z_0-9:]*\\)[#.][^ \n]*")
	  (intern (match-string 1) klass-table)
	  (skip-chars-forward "^ \n")
	  (skip-chars-forward " \n"))
	(if (= (point) (point-min))
	    nil ; (message "no such class or method: %s" word)
	  (setq klass (completing-read "Class: "
				       klass-table nil nil nil nil nil))
	  (erase-buffer)
	  (refe-call-process buf "--all" klass word))))

    (set-buffer-modified-p nil)
    (setq buffer-read-only t)
    (goto-char (point-min))
    (display-buffer buf)))

(provide 'refe)

以前書いたように refe.el のライセンスは、なんともフワフワした状態なので要注意。

refe.e の作成

こんなスクリプトを用意した。

#! /usr/local/bin/bash

RUBY=/usr/local/bin/ruby19

sexp() {
  $RUBY -ne 'puts %Q<# (refe "#{$_.strip.gsub(/"/, %q[\\"]).sub(/\.#/, %q[.])}")>'
}

classes() {
  refe -l
}

methods() {
  refe -l ''
}

(classes; methods) | sexp

やってることは rubikitch さんと同じ (はず)

実行結果を refe.e に保存する。

anything の設定

上記 rubikitch さんのエントリからパクる。
elisp で , とか ` とか出てきたら、もうお手上げ。


毎月公開される「るりま」のスナップショットが出たら refe.e だけ作り直せば OK.