メニュー

関連ページリンク

トップ > google > google - 人気ブログ(Blog)検索結果詳細 (2008年12月4日 1時)

Luaのはまりどころ

マイクロスレッドの比較対象のひとつとしてlua5.1を使ってみたけど、Luaって最近の言語と比べて結構独特な部分が多いなと思いました。以下、はまったところ:

  • Tableを配列風に使うときの開始インデックスは1であることを忘れる
  • Tableの存在しないインデックスの値や未定義変数の値もnilが返る
    • nilを値として使いにくい(ls = {nil, nil}; print(#ls) --=> 0)
  • cls.method(self) と書くところを cls:method(self) と書いても、その間違いに気がつきにくい
  • localを書き忘れる

coroutine用channel実装と、生産者消費者例

-- many-to-many channel
Channel = {}
function Channel:new()
   local obj = setmetatable({}, {__index = self})
   obj:init()
   return obj
end
function Channel:init()
   self.paused_sender = setmetatable({}, {__index = table})
   self.paused_receiver = setmetatable({}, {__index = table})
   self.msg = nil
end
function Channel:send(msg)
   if self.msg ~= nil then
      -- pause sender when buffer queue filled up
      self.paused_sender:insert(coroutine.running())
      coroutine.yield()
   end
   self.msg = msg
   if #self.paused_receiver > 0 then
      -- resume paused receiver
      local paused = self.paused_receiver:remove(1) -- lua index start with 1
      coroutine.resume(paused)
   end
end
function Channel:recv()
   if self.msg == nil then
      -- pause receiver when buffer queue empty
      self.paused_receiver:insert(coroutine.running())
      coroutine.yield()
   end
   local msg = self.msg
   self.msg = nil
   if #self.paused_sender > 0 then
      -- resume paused sender
      local paused = self.paused_sender:remove(1)
      coroutine.resume(paused)
   end
   return msg
end

-- examples
prod = coroutine.wrap(
   function (ch)
      print("prod start")
      for i = 1, 10 do
         ch:send(i * 2)
      end
      ch:send(-1)
      print("prod end")
   end)

cons = coroutine.wrap(
   function (ch)
      print("cons start")
      while true do
         local msg = ch:recv()
         if msg == -1 then
            break
         end
         print("rcv: " .. msg)
      end
      print("cons end")
   end)

ch = Channel:new()
prod(ch)
cons(ch)

幾分冗長な実装です。yield/resumeではパラメータを受け渡し可能なので、工夫すればmsgは不要になるでしょう。queueも実質ひとつでよく、senderかreceiver化どちらかをため続けるようにすればいいはず。

作者:bellbind

更新日:2008年11月27日 18時55分

このブログのホーム

0usersってどういうことでしょうね

新はてブの本文埋め込みがうざいので、Stylishで消してみる

新しいはてブは一覧ページでも本文を埋め込むようになったけど、どうもこの本文はノイズが多すぎて、一覧性にも支障があるので、Stylishを使ってユーザーCSSで消すことにしました。

タグで絞り込むと非表示表示を切り替えられるのに、絞り込まないとそれがないってのはおかしくないか?

ステータスバーのStylishアイコンをクリックして、「スタイルを書く」-「b.hatena.ne.jp用」を選び、CSSを以下のようにしました

@namespace url(http://www.w3.org/1999/xhtml);

@-moz-document domain("b.hatena.ne.jp") {
div.entry-body blockquote {
  display: none;
}
}

これですっきりしました。新デザインも意外とよくね?っと思ったけど、最初のエントリまでの段は1行で十分で3行もある(全部「注目」ばっか)のは余計だ。

f:id:bellbind:20081125185317p:image

ほかにもいろいろ問題あるけど、ベータテストで出てこなかったのかな。。。

作者:bellbind

更新日:2008年11月25日 18時51分

このブログのホーム

merbを使ってみる

rails風にwebアプリを作成できる話題のmerbを使ってみました。

インストール

すでにgem,rails,mongrelが入っているcygwin上にいれてみました*1

まずはgemの更新

gem update
gem clean

そしてmerbを入れる

gem install merb

入ったのはmerb-1.0.1でした。デフォルトでmerb_datamapper、do_sqlite3が入るようです。しかし、riの生成段階でmerb-slicesが失敗しました。

そこでmerb-slicesを削除して、rdoc生成しなおし

gem uninstall merb-slices
gem rdoc --all

そしてmerb-slicesを入れなおす

gem install merb-slices

プロジェクト作成

インストールすると、コード生成用のmerb-genと実行用のmerbコマンドが入ります。

アプリケーション生成では、rails風なディレクトリ構成であるappと、より単純化したflatなどがあるようです。今回はappでやってみます。

merb-gen app myhello
cd myhello

実行

merbコマンドでwebサーバが立ち上がります

merb

cygwinの場合、/home/xxxがrwxrwxrwxだとグループ書き込みが危険だよエラーが出て、停止します。そこでchmodしておき、再度merbを実行しました。

chmod 755 ~
merb

メッセージががーっとでてポート4000番で立ち上がったようです。

そこでブラウザでhttp://localhost:4000/にアクセスすると、「Flesh Merb App」ページが表示され、

Exception:

No routes match the request: /

となってました。

いろいろ作成

最初のモデルオブジェクトとしてブックマークアプリ風なlinkをresourceで作ってみます。

$ merb-gen resource link
Loading ...
     [ADDED]  spec/models/link_spec.rb
     [ADDED]  app/models/link.rb
     [ADDED]  spec/requests/links_spec.rb
     [ADDED]  app/controllers/links.rb
     [ADDED]  app/views/links/index.html.erb
     [ADDED]  app/views/links/show.html.erb
     [ADDED]  app/views/links/edit.html.erb
     [ADDED]  app/views/links/new.html.erb
     [ADDED]  app/helpers/links_helper.rb
resources :links route added to config/router.rb

というファイルが追加されます。表示メッセージが色つきなのも面白い。

動かしてるmerbサーバも自動で適用するようで、そのままで http://localhost:4000/links/ にアクセスすると、index.html.erbの内容が表示されます( http://wiki.merbivore.com/howto/crud_view_example_with_merb_using_erb みて編集しろという素っ気ないものですけど)。

model編集

resourceで生成されたlink.rbの内容は以下のようになってました。

class Link
  include DataMapper::Resource
  
  property :id, Serial
  
  
end

url、titleとタイムスタンプを入れてみましょう。

class Link
  include DataMapper::Resource
  
  property :id, Serial
  
  property :url, URI, :nullable => false, :writer => :protected
  property :title, Text, :nullable => false, :default => "no title", :lazy => false
  property :timestamp, DateTime, :nullable => false
  
  validates_is_unique :url
end

単一モデルでなるべく制約を入れるようにやってみました。

ソースコードの更新は自動で反映するようですが、エラーが出るコードを保存してしまうと、merbは落ちるようです(svn upとかで更新する場合なら有効かも)。

対話環境でデータを入れてみましょう。

merb -i

webratを入れろと出て、終了しました。で、webratを追加しよう

gem install webrat

依存関係の中にネイティブライブラリnokogiriがあり、そのビルドにcygwinでは、libxml2-develとlibxslt-develが必要のようです(setup.exeで入れたらxorgまで入ってしまったorz)。

コンソールを立ち上げる前に一度dbを作ります。

rake db:create
merb -i

以下のようにデータを作ってみます。

link = Link.new :url => "http://www.yahoo.com", :title => "Yahoo!", :timestamp => DateTime.now
link.save
link.id

idが振られていれば成功です。同じurlを持つ別のlinkを作ってsaveして失敗する(falseが返る)のを試してみたりました。

検索はidで検索する場合はget、一つだけならfirst、全部ならallです。

Link.get(1)
Link.first(:url => "http://www.yahoo.com")
Link.all(:url.like => "%yahoo%", :url.like => "%com%") # and 結合

Controller編集

ブラウザでこのLinkオブジェクトの表示や編集を可能にするために、erbファイルを修正することになります。

その前に、timestampの処理をcontrollerに追加しておきます。

生成されたデフォルトのコントローラlinks.rbは以下のようなものです

class Links < Application
  # provides :xml, :yaml, :js

  def index
    @links = Link.all
    display @links
  end

  def show(id)
    @link = Link.get(id)
    raise NotFound unless @link
    display @link
  end

  def new
    only_provides :html
    @link = Link.new
    display @link
  end

  def edit(id)
    only_provides :html
    @link = Link.get(id)
    raise NotFound unless @link
    display @link
  end

  def create(link)
    @link = Link.new(link)
    if @link.save
      redirect resource(@link), :message => {:notice => "Link was successfully created"}
    else
      message[:error] = "Link failed to be created"
      render :new
    end
  end

  def update(id, link)
    @link = Link.get(id)
    raise NotFound unless @link
    if @link.update_attributes(link)
       redirect resource(@link)
    else
      display @link, :edit
    end
  end

  def destroy(id)
    @link = Link.get(id)
    raise NotFound unless @link
    if @link.destroy
      redirect resource(:links)
    else
      raise InternalServerError
    end
  end

end # Links

ここのcreateとupdateにlink.timestamp = DateTime.nowを入れました。

class Links < Application
  # provides :xml, :yaml, :js

  def index
    @links = Link.all
    display @links
  end

  def show(id)
    @link = Link.get(id)
    raise NotFound unless @link
    display @link
  end

  def new
    only_provides :html
    @link = Link.new
    display @link
  end

  def edit(id)
    only_provides :html
    @link = Link.get(id)
    raise NotFound unless @link
    display @link
  end

  def create(link)
    @link = Link.new(link)
    @link.timestamp = DateTime.now
    if @link.save
      redirect resource(@link), :message => {:notice => "Link was successfully created"}
    else
      message[:error] = "Link failed to be created"
      render :new
    end
  end

  def update(id, link)
    @link = Link.get(id)
    raise NotFound unless @link
    @link.timestamp = DateTime.now
    if @link.update_attributes(link)
      redirect resource(@link)
    else
      display @link, :edit
    end
  end

  def destroy(id)
    @link = Link.get(id)
    raise NotFound unless @link
    if @link.destroy
      redirect resource(:links)
    else
      raise InternalServerError
    end
  end

end # Links

良くわからないけど、providesについてるコメントアウトもはずしました。

Viewの編集

erbファイルを、http://wiki.merbivore.com/howto/crud_view_example_with_merb_using_erb を見ながら修正することになります。

erbファイルは4つ(index.html.erb, show.html.erb, new.html.erb, edit.html.erb)あり、最初から埋めておいてもいいんじゃね?という感じではありますが。

index.html.erb
<h1>Links controller, index action</h1>

<table>
  <tr>
    <th>URL</th>
    <th>Title</th>
    
    <th colspan="3">Actions</th>
  </tr>
  
<% @links.each do |link| %>
  <tr>
    <td><%=h link.url %></td>
    <td><%=h link.title %></td>
    <td><%= link_to 'Show', resource(link) %></td>
    <td><%= link_to 'Edit', resource(link, :edit) %></td>
    <td><%= delete_button(link, "Delete") %></td>
  </tr>
<% end %>
</table>

<%= link_to 'New', resource(:links, :new) %>
show.html.erb
<h1>Links controller, show action</h1>

<h3><%=h @link.title %></h3>
<ul>
  <li><%=h @link.url %></li>
  <li><%=h @link.timestamp %></li>
</ul>

<%= link_to 'Back', resource(:links) %>
new.html.erb
<h1>Links controller, new action</h1>

<%= form_for(@link, :action => resource(:links) ) do %>
  <p>
    <%= text_field :url, :label => "URL" %>
  </p>
  <p>
    <%= text_field :title, :label => "Title" %>
  </p>
  <p>
    <%= submit "Create" %>
  </p>
<% end =%>
 
<%= link_to 'Back', resource(:links) %>
edit.html.erb
<h1>Links controller, edit action</h1>


<%= form_for(@link, :action => resource(@link) ) do %>
  <p>
    <%=h @link.url %>
  </p>
  <p>
    <%= text_field :title, :label => "Title" %>
  </p>
  <p>
    <%= submit "Update" %>
  </p>
<% end =%>
 
<%= link_to 'Back', resource(:links) %>

ブラウザで編集ができるようになりました。

感想

自動テストにあたるものがRSpecだったりと、railsとは若干セマンティクスが違う部分があります。けど、手順としては大体同じようにできるので、ドキュメントを探しながらやっていけば、railsの経験があればかんたんに使えるようになるのではないでしょうか(実際、上に上げた二つのドキュメントのリンクとrake --tasksくらいの情報だけでここまでできたし)。

ドキュメントに関しては、railsよりはよいのではないかなとも思います。とくにActiveRecordとくらべると、DataMapperはいろいろ探しやすい感じでした。


merbの機能としては、appのように作ってappに組み込むことができるようにするsliceとかあって、いろいろ面白そうです。

タグやコメント、アカウント管理のようなシステムはpluginとかでは(コードの更新等で)かなり苦しいのですが、sliceならこのあたりを含めてうまくできそうな気がします(本当にそうかは調べてないですけど)。

*1:cygwinでrailsに必要なパッケージは、ruby, gcc, make, swig, sqlite3, libsqlite3-develなど

作者:bellbind

更新日:2008年11月21日 9時48分

このブログのホーム

gccでのx86とamd64の時のsizeofの値

linux上でgcc-4.3を使った場合。違いはptr型、long、long double、size_t、time_tなどで起こる。

x86

sizeof(int) = 4
sizeof(void *) = 4
sizeof(size_t) = 4
sizeof(ptrdiff_t) = 4
sizeof(wchar_t) = 4
sizeof(void) = 1
sizeof(clock_t) = 4
sizeof(time_t) = 4
sizeof(FILE) = 148
sizeof(fpos_t) = 12
sizeof(div_t) = 8
sizeof(char) = 1
sizeof(short int) = 2
sizeof(long int) = 4
sizeof(long long int) = 8
sizeof(float) = 4
sizeof(double) = 8
sizeof(long double) = 12
sizeof(_Bool) = 1
sizeof(bool) = 1
sizeof(_Complex) = 16
sizeof(float complex) = 8
sizeof(double complex) = 16
sizeof(long double complex) = 24

amd64

sizeof(int) = 4
sizeof(void *) = 8
sizeof(size_t) = 8
sizeof(ptrdiff_t) = 8
sizeof(wchar_t) = 4
sizeof(void) = 1
sizeof(clock_t) = 8
sizeof(time_t) = 8
sizeof(FILE) = 216
sizeof(fpos_t) = 16
sizeof(div_t) = 8
sizeof(char) = 1
sizeof(short int) = 2
sizeof(long int) = 8
sizeof(long long int) = 8
sizeof(float) = 4
sizeof(double) = 8
sizeof(long double) = 16
sizeof(_Bool) = 1
sizeof(bool) = 1
sizeof(_Complex) = 16
sizeof(float complex) = 8
sizeof(double complex) = 16
sizeof(long double complex) = 32

ソース: sizeof.c

// gcc -std=c99 -Wall sizeof.c
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <complex.h>
#include <wchar.h>
#include <time.h>
#include <fenv.h>
#include <inttypes.h>

#define size(type) printf("sizeof(" #type ") = %" PRIuPTR "\n", sizeof(type))

int main()
{
  size(int);
  size(void *);
  size(size_t);
  size(ptrdiff_t);
  size(wchar_t);
  size(void);
  size(clock_t);
  size(time_t);
  size(FILE);
  size(fpos_t);
  size(div_t);
  
  size(char);
  size(short int);
  size(long int);
  size(long long int);
  size(float);
  size(double);
  size(long double);
  
  size(_Bool);
  size(bool);
  size(_Complex);
  size(float complex);
  size(double complex);
  size(long double complex);
  //size(_Imaginary); // not support on gcc-4.3
  
  return 0;
}

size_t型の値表示のprintfフォーマットコードは、inttypes.hのPRIuPTRを使うので正解らしい。

作者:bellbind

更新日:2008年11月19日 3時28分

このブログのホーム

つづき、文字単位コピーからブロック単位コピーにする

fgetcで書ければ、freadで書ける - ラシウラでは、fgetcからfreadを使うように変えました。これは機械的に変換しただけでしたが、ここからつぎは1文字づつコピーしている部分を、memcpyを使用して、ブロック単位でコピーするように変えてみましょう。

まずリファクタリング

まず、readlines中のバッファ確保の部分を関数として外に出し、readlinesをすっきりさせます。

char ** readlines(FILE * file)
{
  enum {LINES_SIZE = 128};
  enum {BUF_SIZE = 1024};
  enum {CR = '\r'};
  enum {LF = '\n'};
  
  char ** lines = NULL;
  int lineindex = 0;
  int lines_size = LINES_SIZE;
  int index = 0;
  int line_size = 0;
  int is_prev_cr = 0;
  char buf[BUF_SIZE];
  
  assert(file != NULL);
  lines = malloc(lines_size * sizeof(char *));
  if (lines == NULL) goto error;
  lines[0] = NULL;
  for (;;) {
    int i = 0;
    int count = fread(buf, sizeof(char), BUF_SIZE, file);
    for (i = 0; i < count; i++) {
      char ch = buf[i];
      if (is_prev_cr) {
        is_prev_cr = 0;
        if (ch == LF) continue;
      }
      
      if (!expand_lines(&lines, lineindex, &lines_size)) goto error;
      if (!alloc_line(lines, lineindex, &line_size)) goto error;
      if (!expand_line(&lines[lineindex], index, &line_size)) goto error;
      
      if (ch == CR) is_prev_cr = 1;
      if (ch == CR || ch == LF) {
        realloc(lines[lineindex], (index + 1) * sizeof(char));
        lineindex++;
        index = 0;
      } else {
        lines[lineindex][index] = (char) ch;
        index++;
        lines[lineindex][index] = '\0';
      }
    }
    if (feof(file)) break;
  }
  realloc(lines, (lineindex + 2) * sizeof(char *));
  return lines;
error:
  freelines(lines);
  return NULL;
}

expand_lines、alloc_line、expand_lineとして切り分けました。これらは以下のように実装です。更新する変数はそのポインタが渡るようにするくらいであり、をこれも機械的に行っています。

static int expand_lines(char *** plines, int lineindex, int * plines_size)
{
  if (lineindex + 1 == *plines_size) {
    char ** expanded;
    *plines_size *= 2;
    expanded = realloc(*plines, *plines_size * sizeof(char *));
    if (expanded == NULL) return 0;
    *plines = expanded;
  }
  return 1;
}
static int alloc_line(char ** lines, int lineindex, int* pline_size)
{
  enum {LINE_SIZE = 128};
  if (lines[lineindex] == NULL) {
    *pline_size = LINE_SIZE;
    lines[lineindex] = malloc(*pline_size * sizeof(char));
    if (lines[lineindex] == NULL) return 0;
    lines[lineindex + 1] = NULL;
    lines[lineindex][0] = '\0';
  }
  return 1;
}
static int expand_line(char ** pline, int index, int * pline_size)
{
  if (index + 1 == *pline_size) {
    char * expanded;
    *pline_size *= 2;
    expanded = realloc(*pline, *pline_size * sizeof(char));
    if (expanded == NULL) return 0;
    *pline = expanded;
  }
  return 1;
}

ブロック単位コピー化する

バッファbufをブロック単位でコピーするためには、そのコピー範囲が必要です。

そこでループ中の処理は、コピー範囲(つまり、始点と終点)を探すものに変わります。

そして、いつコピーするか、を考える必要がでてきます。これは、改行が出たとき、そして、バッファの終端に届いたとき、の二箇所で行うことになります。

擬似コードでは、以下のようになるでしょう.

for (;;) {
  int 始点 = 0, 終点 = 0;
  count = fread(buf, sizeof(char), BUF_SIZE, file);
  for (i = 0; i < count; i++) {
    char ch = buf[i];
    if (前がCR && ch == LF) {
       始点、終点ともに1進む;
       continue;
    }
    if (ch == CR || ch == LF) {
       現在行バッファを確保、拡張;
       現在行バッファにbuf[始点...終点]をコピー;
       次の行にいく;
       終点が1進む;
       始点 = 終点;
    } else {
       終点が1進む;
    }
  }
  if (未コピー分がある場合) {
    現在行バッファを確保、拡張;
    現在行バッファにbuf[始点...終点]をコピー;
  }
  if (feof(file)) break;
}

reallocでは、第一引数がNULLの場合、mallocと同じ動作になります。

前出のコードでは、行バッファは初期サイズから倍倍にしていくため、行バッファの確保と拡張を別々にしていました。

今回確保でも拡張でもコピーする分だけ確保できれば良く、確保すべき長さがわかっているため、確保も拡張もreallocで行うよう統一した、realloc_line関数で行うことにします。

readlines関数は以下のようになります。

char ** readlines(FILE * file)
{
  enum {LINES_SIZE = 25};
  enum {BUF_SIZE = 1024};
  enum {CR = '\r'};
  enum {LF = '\n'};
  
  char ** lines = NULL;
  int lineindex = 0;
  int lines_size = LINES_SIZE;
  int index = 0;
  int is_prev_cr = 0;
  char buf[BUF_SIZE];
  
  assert(file != NULL);
  lines = malloc(lines_size * sizeof(char *));
  if (lines == NULL) goto error;
  lines[0] = NULL;
  for (;;) {
    int i = 0;
    int start = 0, end = 0;
    int count = fread(buf, sizeof(char), BUF_SIZE, file);
    for (i = 0; i < count; i++) {
      char ch = buf[i];
      if (is_prev_cr) {
        is_prev_cr = 0;
        if (ch == LF) {
          end++;
          start++;
          continue;
        }
      }
      
      if (ch == CR) is_prev_cr = 1;
      if (ch == CR || ch == LF) {
        int len = end - start;
        if (!expand_lines(&lines, lineindex, &lines_size)) goto error;
        if (!realloc_line(lines, lineindex, index + len + 1)) goto error;
        memcpy(&lines[lineindex][index], &buf[start], len);
        lines[lineindex][index + len] = '\0';
        
        lineindex++;
        index = 0;
        end++;
        start = end;
      } else {
        end++;
      }
    }
    if (start != end) {
      int len = end - start;
      if (!expand_lines(&lines, lineindex, &lines_size)) goto error;
      if (!realloc_line(lines, lineindex, index + len + 1)) goto error;
      memcpy(&lines[lineindex][index], &buf[start], len);
      lines[lineindex][index + len] = '\0';
      
      index += len;
    }
    if (feof(file)) break;
  }
  realloc(lines, (lineindex + 2) * sizeof(char *));
  return lines;
error:
  freelines(lines);
  return NULL;
}
static int realloc_line(char ** lines, int lineindex, int line_size)
{
  char * buf = realloc(lines[lineindex], line_size * sizeof(char));
  if (buf == NULL) return 0;
  lines[lineindex] = buf;
  lines[lineindex + 1] = NULL;
  return 1;
}

バッファ拡張の部分は2箇所とも同じなので、同様に(copy_to_lineなどと名づけた関数にでも)一つにまとめれば、意外とすっきりしたコードになるでしょう。

いきなりこの関数実装をみてブロック処理がわからなくても、fgetcで一文字づつ処理する実装から変化させていくのを段階的に見ていけば、ブロック単位処理も難しいものではないことがわかるでしょう。

java.nioのBufferを使う処理も、このような処理方法で行うことになるでしょう。


put it together

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

char ** readlines(FILE *);
void putlines(char **);
void freelines(char **);

int main(int argc, char ** argv)
{
  FILE * file = NULL;
  char ** lines = NULL;
  char * filename = NULL;
  if (argc != 2) return EXIT_FAILURE;
  filename = argv[1];
  file = fopen(filename, "r");
  lines = readlines(file);
  fclose(file);
  putlines(lines);
  freelines(lines);
  return EXIT_SUCCESS;
}

static int realloc_line(char ** lines, int linecount, int line_size);
static int expand_lines(char *** plines, int lineindex, int * plines_size);
static int copy_line(
  char ** lines, int lineindex, int index,
  char * buf, int start, int end);

char ** readlines(FILE * file)
{
  enum {LINES_SIZE = 25};
  enum {BUF_SIZE = 1024};
  enum {CR = '\r'};
  enum {LF = '\n'};
  
  char ** lines = NULL;
  int lineindex = 0;
  int lines_size = LINES_SIZE;
  int index = 0;
  int is_prev_cr = 0;
  char buf[BUF_SIZE];
  
  assert(file != NULL);
  lines = malloc(lines_size * sizeof(char *));
  if (lines == NULL) goto error;
  lines[0] = NULL;
  for (;;) {
    int i = 0;
    int start = 0, end = 0;
    int count = fread(buf, sizeof(char), BUF_SIZE, file);
    for (i = 0; i < count; i++) {
      char ch = buf[i];
      if (is_prev_cr) {
        is_prev_cr = 0;
        if (ch == LF) {
          end++;
          start++;
          continue;
        }
      }
      
      if (ch == CR) is_prev_cr = 1;
      if (ch == CR || ch == LF) {
        if (!expand_lines(&lines, lineindex, &lines_size)) goto error;
        if (!copy_line(lines, lineindex, index, buf, start, end)) goto error;
        
        lineindex++;
        index = 0;
        end++;
        start = end;
      } else {
        end++;
      }
    }
    if (start != end) {
      if (!expand_lines(&lines, lineindex, &lines_size)) goto error;
      if (!copy_line(lines, lineindex, index, buf, start, end)) goto error;
      
      index += end - start;
    }
    if (feof(file)) break;
  }
  realloc(lines, (lineindex + 2) * sizeof(char *));
  return lines;
error:
  freelines(lines);
  return NULL;
}

void freelines(char ** lines)
{
  char ** cursor;
  if (lines == NULL) return;
  for (cursor = lines; *cursor; ++cursor) free(*cursor);
  free(lines);
}

void putlines(char ** lines)
{
  char ** cursor;
  if (lines == NULL) return;
  for (cursor = lines; *cursor; ++cursor) puts(*cursor);
}

static int copy_line(
  char ** lines, int lineindex, int index,
  char * buf, int start, int end)
{
  int len = end - start;
  if (!realloc_line(lines, lineindex, index + len + 1)) return 0;
  memcpy(&lines[lineindex][index], &buf[start], len);
  lines[lineindex][index + len] = '\0';
  return 1;
}

static int realloc_line(char ** lines, int lineindex, int line_size)
{
  char * buf = realloc(lines[lineindex], line_size * sizeof(char));
  if (buf == NULL) return 0;
  lines[lineindex] = buf;
  lines[lineindex + 1] = NULL;
  return 1;
}

static int expand_lines(char *** plines, int lineindex, int * plines_size)
{
  if (lineindex + 1 == *plines_size) {
    char ** expanded;
    *plines_size *= 2;
    expanded = realloc(*plines, *plines_size * sizeof(char *));
    if (expanded == NULL) return 0;
    *plines = expanded;
  }
  return 1;
}

余談: fread用バッファを用意せず、行バッファで読み込むようにする、と

freadから取り込むバッファを削ることも可能です。コピー回数とスタックサイズは減りますが、コードは若干複雑化し、おそらくバッファ確保回数も増えるので、パフォーマンスは落ちるでしょう。

char ** readlines(FILE * file)
{
  enum {LINES_SIZE = 25};
  enum {BUF_SIZE = 1024};
  enum {CR = '\r'};
  enum {LF = '\n'};
  
  char ** lines = NULL;
  int lineindex = 0;
  int lines_size = LINES_SIZE;
  int index = 0;
  int is_prev_cr = 0;
  char * buf = NULL;
  
  assert(file != NULL);
  lines = malloc(lines_size * sizeof(char *));
  if (lines == NULL) goto error;
  lines[0] = NULL;
  for (;;) {
    int i = 0;
    int start = 0, end = 0;
    int count;
    int buf_line_index;
    int buf_line_size;
    
    if (!expand_lines(&lines, lineindex, &lines_size)) goto error;
    if (!realloc_line(lines, lineindex, index + BUF_SIZE)) goto error;
    
    buf_line_index = lineindex;
    buf_line_size = index;
    buf = &lines[lineindex][index];
    count = fread(buf, sizeof(char), BUF_SIZE, file);
    for (i = 0; i < count; i++) {
      char ch = buf[i];
      if (is_prev_cr) {
        is_prev_cr = 0;
        if (ch == LF) {
          end++;
          start++;
          continue;
        }
      }
      
      if (ch == CR) is_prev_cr = 1;
      if (ch == CR || ch == LF) {
        int len = end - start;
        if (buf_line_index != lineindex) {
          if (!expand_lines(&lines, lineindex, &lines_size)) goto error;
          if (!realloc_line(lines, lineindex, index + len + 1)) goto error;
          memcpy(&lines[lineindex][index], &buf[start], len);
        } else {
          buf_line_size += len;
        }
        lines[lineindex][index + len] = '\0';
        lineindex++;
        index = 0;
        end++;
        start = end;
      } else {
        end++;
      }
    }
    if (start != end) {
      int len = end - start;
      if (!expand_lines(&lines, lineindex, &lines_size)) goto error;
      if (!realloc_line(lines, lineindex, index + len + 1)) goto error;
      memmove(&lines[lineindex][index], &buf[start], len);
      lines[lineindex][index + len] = '\0';
      index += len;
    }
    if (buf_line_index != lineindex) {
      realloc(lines[buf_line_index], buf_line_size * sizeof(char));
    }
    if (feof(file)) break;
  }
  realloc(lines, (lineindex + 2) * sizeof(char *));
  return lines;
error:
  freelines(lines);
  return NULL;
}

これはやりすぎの例でしょう。

作者:bellbind

更新日:2008年11月12日 21時10分

このブログのホーム

Luaのはまりどころ

マイクロスレッドの比較対象のひとつとしてlua5.1を使ってみたけど、Luaって最近の言語と比べて結構独特な部分が多いなと思いました。以下、はまったところ:

  • Tableを配列風に使うときの開始インデックスは1であることを忘れる
  • Tableの存在しないインデックスの値や未定義変数の値もnilが返る
    • nilを値として使いにくい(ls = {nil, nil}; print(#ls) --=> 0)
  • cls.method(self) と書くところを cls:method(self) と書いても、その間違いに気がつきにくい
  • localを書き忘れる

coroutine用channel実装と、生産者消費者例

-- many-to-many channel
Channel = {}
function Channel:new()
   local obj = setmetatable({}, {__index = self})
   obj:init()
   return obj
end
function Channel:init()
   self.paused_sender = setmetatable({}, {__index = table})
   self.paused_receiver = setmetatable({}, {__index = table})
   self.msg = nil
end
function Channel:send(msg)
   if self.msg ~= nil then
      -- pause sender when buffer queue filled up
      self.paused_sender:insert(coroutine.running())
      coroutine.yield()
   end
   self.msg = msg
   if #self.paused_receiver > 0 then
      -- resume paused receiver
      local paused = self.paused_receiver:remove(1) -- lua index start with 1
      coroutine.resume(paused)
   end
end
function Channel:recv()
   if self.msg == nil then
      -- pause receiver when buffer queue empty
      self.paused_receiver:insert(coroutine.running())
      coroutine.yield()
   end
   local msg = self.msg
   self.msg = nil
   if #self.paused_sender > 0 then
      -- resume paused sender
      local paused = self.paused_sender:remove(1)
      coroutine.resume(paused)
   end
   return msg
end

-- examples
prod = coroutine.wrap(
   function (ch)
      print("prod start")
      for i = 1, 10 do
         ch:send(i * 2)
      end
      ch:send(-1)
      print("prod end")
   end)

cons = coroutine.wrap(
   function (ch)
      print("cons start")
      while true do
         local msg = ch:recv()
         if msg == -1 then
            break
         end
         print("rcv: " .. msg)
      end
      print("cons end")
   end)

ch = Channel:new()
prod(ch)
cons(ch)

幾分冗長な実装です。yield/resumeではパラメータを受け渡し可能なので、工夫すればmsgは不要になるでしょう。queueも実質ひとつでよく、senderかreceiver化どちらかをため続けるようにすればいいはず。

作者:bellbind

更新日:2008年11月27日 9時55分

このブログのホーム

0usersってどういうことでしょうね

新はてブの本文埋め込みがうざいので、Stylishで消してみる

新しいはてブは一覧ページでも本文を埋め込むようになったけど、どうもこの本文はノイズが多すぎて、一覧性にも支障があるので、Stylishを使ってユーザーCSSで消すことにしました。

タグで絞り込むと非表示表示を切り替えられるのに、絞り込まないとそれがないってのはおかしくないか?

ステータスバーのStylishアイコンをクリックして、「スタイルを書く」-「b.hatena.ne.jp用」を選び、CSSを以下のようにしました

@namespace url(http://www.w3.org/1999/xhtml);

@-moz-document domain("b.hatena.ne.jp") {
div.entry-body blockquote {
  display: none;
}
}

これですっきりしました。新デザインも意外とよくね?っと思ったけど、最初のエントリまでの段は1行で十分で3行もある(全部「注目」ばっか)のは余計だ。

f:id:bellbind:20081125185317p:image

ほかにもいろいろ問題あるけど、ベータテストで出てこなかったのかな。。。

作者:bellbind

更新日:2008年11月25日 9時51分

このブログのホーム

merbを使ってみる

rails風にwebアプリを作成できる話題のmerbを使ってみました。

インストール

すでにgem,rails,mongrelが入っているcygwin上にいれてみました*1

まずはgemの更新

gem update
gem clean

そしてmerbを入れる

gem install merb

入ったのはmerb-1.0.1でした。デフォルトでmerb_datamapper、do_sqlite3が入るようです。しかし、riの生成段階でmerb-slicesが失敗しました。

そこでmerb-slicesを削除して、rdoc生成しなおし

gem uninstall merb-slices
gem rdoc --all

そしてmerb-slicesを入れなおす

gem install merb-slices

プロジェクト作成

インストールすると、コード生成用のmerb-genと実行用のmerbコマンドが入ります。

アプリケーション生成では、rails風なディレクトリ構成であるappと、より単純化したflatなどがあるようです。今回はappでやってみます。

merb-gen app myhello
cd myhello

実行

merbコマンドでwebサーバが立ち上がります

merb

cygwinの場合、/home/xxxがrwxrwxrwxだとグループ書き込みが危険だよエラーが出て、停止します。そこでchmodしておき、再度merbを実行しました。

chmod 755 ~
merb

メッセージががーっとでてポート4000番で立ち上がったようです。

そこでブラウザでhttp://localhost:4000/にアクセスすると、「Flesh Merb App」ページが表示され、

Exception:

No routes match the request: /

となってました。

いろいろ作成

最初のモデルオブジェクトとしてブックマークアプリ風なlinkをresourceで作ってみます。

$ merb-gen resource link
Loading ...
     [ADDED]  spec/models/link_spec.rb
     [ADDED]  app/models/link.rb
     [ADDED]  spec/requests/links_spec.rb
     [ADDED]  app/controllers/links.rb
     [ADDED]  app/views/links/index.html.erb
     [ADDED]  app/views/links/show.html.erb
     [ADDED]  app/views/links/edit.html.erb
     [ADDED]  app/views/links/new.html.erb
     [ADDED]  app/helpers/links_helper.rb
resources :links route added to config/router.rb

というファイルが追加されます。表示メッセージが色つきなのも面白い。

動かしてるmerbサーバも自動で適用するようで、そのままで http://localhost:4000/links/ にアクセスすると、index.html.erbの内容が表示されます( http://wiki.merbivore.com/howto/crud_view_example_with_merb_using_erb みて編集しろという素っ気ないものですけど)。

model編集

resourceで生成されたlink.rbの内容は以下のようになってました。

class Link
  include DataMapper::Resource
  
  property :id, Serial
  
  
end

url、titleとタイムスタンプを入れてみましょう。

class Link
  include DataMapper::Resource
  
  property :id, Serial
  
  property :url, URI, :nullable => false, :writer => :protected
  property :title, Text, :nullable => false, :default => "no title", :lazy => false
  property :timestamp, DateTime, :nullable => false
  
  validates_is_unique :url
end

単一モデルでなるべく制約を入れるようにやってみました。

ソースコードの更新は自動で反映するようですが、エラーが出るコードを保存してしまうと、merbは落ちるようです(svn upとかで更新する場合なら有効かも)。

対話環境でデータを入れてみましょう。

merb -i

webratを入れろと出て、終了しました。で、webratを追加しよう

gem install webrat

依存関係の中にネイティブライブラリnokogiriがあり、そのビルドにcygwinでは、libxml2-develとlibxslt-develが必要のようです(setup.exeで入れたらxorgまで入ってしまったorz)。

コンソールを立ち上げる前に一度dbを作ります。

rake db:create
merb -i

以下のようにデータを作ってみます。

link = Link.new :url => "http://www.yahoo.com", :title => "Yahoo!", :timestamp => DateTime.now
link.save
link.id

idが振られていれば成功です。同じurlを持つ別のlinkを作ってsaveして失敗する(falseが返る)のを試してみたりました。

検索はidで検索する場合はget、一つだけならfirst、全部ならallです。

Link.get(1)
Link.first(:url => "http://www.yahoo.com")
Link.all(:url.like => "%yahoo%", :url.like => "%com%") # and 結合

Controller編集

ブラウザでこのLinkオブジェクトの表示や編集を可能にするために、erbファイルを修正することになります。

その前に、timestampの処理をcontrollerに追加しておきます。

生成されたデフォルトのコントローラlinks.rbは以下のようなものです

class Links < Application
  # provides :xml, :yaml, :js

  def index
    @links = Link.all
    display @links
  end

  def show(id)
    @link = Link.get(id)
    raise NotFound unless @link
    display @link
  end

  def new
    only_provides :html
    @link = Link.new
    display @link
  end

  def edit(id)
    only_provides :html
    @link = Link.get(id)
    raise NotFound unless @link
    display @link
  end

  def create(link)
    @link = Link.new(link)
    if @link.save
      redirect resource(@link), :message => {:notice => "Link was successfully created"}
    else
      message[:error] = "Link failed to be created"
      render :new
    end
  end

  def update(id, link)
    @link = Link.get(id)
    raise NotFound unless @link
    if @link.update_attributes(link)
       redirect resource(@link)
    else
      display @link, :edit
    end
  end

  def destroy(id)
    @link = Link.get(id)
    raise NotFound unless @link
    if @link.destroy
      redirect resource(:links)
    else
      raise InternalServerError
    end
  end

end # Links

ここのcreateとupdateにlink.timestamp = DateTime.nowを入れました。

class Links < Application
  # provides :xml, :yaml, :js

  def index
    @links = Link.all
    display @links
  end

  def show(id)
    @link = Link.get(id)
    raise NotFound unless @link
    display @link
  end

  def new
    only_provides :html
    @link = Link.new
    display @link
  end

  def edit(id)
    only_provides :html
    @link = Link.get(id)
    raise NotFound unless @link
    display @link
  end

  def create(link)
    @link = Link.new(link)
    @link.timestamp = DateTime.now
    if @link.save
      redirect resource(@link), :message => {:notice => "Link was successfully created"}
    else
      message[:error] = "Link failed to be created"
      render :new
    end
  end

  def update(id, link)
    @link = Link.get(id)
    raise NotFound unless @link
    @link.timestamp = DateTime.now
    if @link.update_attributes(link)
      redirect resource(@link)
    else
      display @link, :edit
    end
  end

  def destroy(id)
    @link = Link.get(id)
    raise NotFound unless @link
    if @link.destroy
      redirect resource(:links)
    else
      raise InternalServerError
    end
  end

end # Links

良くわからないけど、providesについてるコメントアウトもはずしました。

Viewの編集

erbファイルを、http://wiki.merbivore.com/howto/crud_view_example_with_merb_using_erb を見ながら修正することになります。

erbファイルは4つ(index.html.erb, show.html.erb, new.html.erb, edit.html.erb)あり、最初から埋めておいてもいいんじゃね?という感じではありますが。

index.html.erb
<h1>Links controller, index action</h1>

<table>
  <tr>
    <th>URL</th>
    <th>Title</th>
    
    <th colspan="3">Actions</th>
  </tr>
  
<% @links.each do |link| %>
  <tr>
    <td><%=h link.url %></td>
    <td><%=h link.title %></td>
    <td><%= link_to 'Show', resource(link) %></td>
    <td><%= link_to 'Edit', resource(link, :edit) %></td>
    <td><%= delete_button(link, "Delete") %></td>
  </tr>
<% end %>
</table>

<%= link_to 'New', resource(:links, :new) %>
show.html.erb
<h1>Links controller, show action</h1>

<h3><%=h @link.title %></h3>
<ul>
  <li><%=h @link.url %></li>
  <li><%=h @link.timestamp %></li>
</ul>

<%= link_to 'Back', resource(:links) %>
new.html.erb
<h1>Links controller, new action</h1>

<%= form_for(@link, :action => resource(:links) ) do %>
  <p>
    <%= text_field :url, :label => "URL" %>
  </p>
  <p>
    <%= text_field :title, :label => "Title" %>
  </p>
  <p>
    <%= submit "Create" %>
  </p>
<% end =%>
 
<%= link_to 'Back', resource(:links) %>
edit.html.erb
<h1>Links controller, edit action</h1>


<%= form_for(@link, :action => resource(@link) ) do %>
  <p>
    <%=h @link.url %>
  </p>
  <p>
    <%= text_field :title, :label => "Title" %>
  </p>
  <p>
    <%= submit "Update" %>
  </p>
<% end =%>
 
<%= link_to 'Back', resource(:links) %>

ブラウザで編集ができるようになりました。

感想

自動テストにあたるものがRSpecだったりと、railsとは若干セマンティクスが違う部分があります。けど、手順としては大体同じようにできるので、ドキュメントを探しながらやっていけば、railsの経験があればかんたんに使えるようになるのではないでしょうか(実際、上に上げた二つのドキュメントのリンクとrake --tasksくらいの情報だけでここまでできたし)。

ドキュメントに関しては、railsよりはよいのではないかなとも思います。とくにActiveRecordとくらべると、DataMapperはいろいろ探しやすい感じでした。


merbの機能としては、appのように作ってappに組み込むことができるようにするsliceとかあって、いろいろ面白そうです。

タグやコメント、アカウント管理のようなシステムはpluginとかでは(コードの更新等で)かなり苦しいのですが、sliceならこのあたりを含めてうまくできそうな気がします(本当にそうかは調べてないですけど)。

*1:cygwinでrailsに必要なパッケージは、ruby, gcc, make, swig, sqlite3, libsqlite3-develなど

作者:bellbind

更新日:2008年11月21日 0時48分

このブログのホーム

gccでのx86とamd64の時のsizeofの値

linux上でgcc-4.3を使った場合。違いはptr型、long、long double、size_t、time_tなどで起こる。

x86

sizeof(int) = 4
sizeof(void *) = 4
sizeof(size_t) = 4
sizeof(ptrdiff_t) = 4
sizeof(wchar_t) = 4
sizeof(void) = 1
sizeof(clock_t) = 4
sizeof(time_t) = 4
sizeof(FILE) = 148
sizeof(fpos_t) = 12
sizeof(div_t) = 8
sizeof(char) = 1
sizeof(short int) = 2
sizeof(long int) = 4
sizeof(long long int) = 8
sizeof(float) = 4
sizeof(double) = 8
sizeof(long double) = 12
sizeof(_Bool) = 1
sizeof(bool) = 1
sizeof(_Complex) = 16
sizeof(float complex) = 8
sizeof(double complex) = 16
sizeof(long double complex) = 24

amd64

sizeof(int) = 4
sizeof(void *) = 8
sizeof(size_t) = 8
sizeof(ptrdiff_t) = 8
sizeof(wchar_t) = 4
sizeof(void) = 1
sizeof(clock_t) = 8
sizeof(time_t) = 8
sizeof(FILE) = 216
sizeof(fpos_t) = 16
sizeof(div_t) = 8
sizeof(char) = 1
sizeof(short int) = 2
sizeof(long int) = 8
sizeof(long long int) = 8
sizeof(float) = 4
sizeof(double) = 8
sizeof(long double) = 16
sizeof(_Bool) = 1
sizeof(bool) = 1
sizeof(_Complex) = 16
sizeof(float complex) = 8
sizeof(double complex) = 16
sizeof(long double complex) = 32

ソース: sizeof.c

// gcc -std=c99 -Wall sizeof.c
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <complex.h>
#include <wchar.h>
#include <time.h>
#include <fenv.h>
#include <inttypes.h>

#define size(type) printf("sizeof(" #type ") = %" PRIuPTR "\n", sizeof(type))

int main()
{
  size(int);
  size(void *);
  size(size_t);
  size(ptrdiff_t);
  size(wchar_t);
  size(void);
  size(clock_t);
  size(time_t);
  size(FILE);
  size(fpos_t);
  size(div_t);
  
  size(char);
  size(short int);
  size(long int);
  size(long long int);
  size(float);
  size(double);
  size(long double);
  
  size(_Bool);
  size(bool);
  size(_Complex);
  size(float complex);
  size(double complex);
  size(long double complex);
  //size(_Imaginary); // not support on gcc-4.3
  
  return 0;
}

size_t型の値表示のprintfフォーマットコードは、inttypes.hのPRIuPTRを使うので正解らしい。

作者:bellbind

更新日:2008年11月18日 18時28分

このブログのホーム

つづき、文字単位コピーからブロック単位コピーにする

fgetcで書ければ、freadで書ける - ラシウラでは、fgetcからfreadを使うように変えました。これは機械的に変換しただけでしたが、ここからつぎは1文字づつコピーしている部分を、memcpyを使用して、ブロック単位でコピーするように変えてみましょう。

まずリファクタリング

まず、readlines中のバッファ確保の部分を関数として外に出し、readlinesをすっきりさせます。

char ** readlines(FILE * file)
{
  enum {LINES_SIZE = 128};
  enum {BUF_SIZE = 1024};
  enum {CR = '\r'};
  enum {LF = '\n'};
  
  char ** lines = NULL;
  int lineindex = 0;
  int lines_size = LINES_SIZE;
  int index = 0;
  int line_size = 0;
  int is_prev_cr = 0;
  char buf[BUF_SIZE];
  
  assert(file != NULL);
  lines = malloc(lines_size * sizeof(char *));
  if (lines == NULL) goto error;
  lines[0] = NULL;
  for (;;) {
    int i = 0;
    int count = fread(buf, sizeof(char), BUF_SIZE, file);
    for (i = 0; i < count; i++) {
      char ch = buf[i];
      if (is_prev_cr) {
        is_prev_cr = 0;
        if (ch == LF) continue;
      }
      
      if (!expand_lines(&lines, lineindex, &lines_size)) goto error;
      if (!alloc_line(lines, lineindex, &line_size)) goto error;
      if (!expand_line(&lines[lineindex], index, &line_size)) goto error;
      
      if (ch == CR) is_prev_cr = 1;
      if (ch == CR || ch == LF) {
        realloc(lines[lineindex], (index + 1) * sizeof(char));
        lineindex++;
        index = 0;
      } else {
        lines[lineindex][index] = (char) ch;
        index++;
        lines[lineindex][index] = '\0';
      }
    }
    if (feof(file)) break;
  }
  realloc(lines, (lineindex + 2) * sizeof(char *));
  return lines;
error:
  freelines(lines);
  return NULL;
}

expand_lines、alloc_line、expand_lineとして切り分けました。これらは以下のように実装です。更新する変数はそのポインタが渡るようにするくらいであり、をこれも機械的に行っています。

static int expand_lines(char *** plines, int lineindex, int * plines_size)
{
  if (lineindex + 1 == *plines_size) {
    char ** expanded;
    *plines_size *= 2;
    expanded = realloc(*plines, *plines_size * sizeof(char *));
    if (expanded == NULL) return 0;
    *plines = expanded;
  }
  return 1;
}
static int alloc_line(char ** lines, int lineindex, int* pline_size)
{
  enum {LINE_SIZE = 128};
  if (lines[lineindex] == NULL) {
    *pline_size = LINE_SIZE;
    lines[lineindex] = malloc(*pline_size * sizeof(char));
    if (lines[lineindex] == NULL) return 0;
    lines[lineindex + 1] = NULL;
    lines[lineindex][0] = '\0';
  }
  return 1;
}
static int expand_line(char ** pline, int index, int * pline_size)
{
  if (index + 1 == *pline_size) {
    char * expanded;
    *pline_size *= 2;
    expanded = realloc(*pline, *pline_size * sizeof(char));
    if (expand