UTF-8 文字列を省略して一定の表示幅に収める

つまり Go の go/runewidth の runewidth.Truncate みたいなことをしたい。

GitHub - mattn/go-runewidth
https://github.com/mattn/go-runewidth

で、どうやら unicode-display_width という gem があるらしい。

GitHub - janlelis/unicode-display_width: Monospace Unicode character width in Ruby
https://github.com/janlelis/unicode-display_width

道具は揃ったので、順当に実装してみる。

require 'unicode/display_width'

def truncate_string(str, len, omission)
  return str.dup unless Unicode::DisplayWidth.of(str) > len
  lim = len - Unicode::DisplayWidth.of(omission)
  n = 0
  chars = str.each_char.take_while{|c| (n += Unicode::DisplayWidth.of(c)) <= lim }
  chars.join << omission
end

ここでちょっと欲が出た。

Unicode::DisplayWidth.of(str) == str.each_char.inject(0){|r, c| r += Unicode::DisplayWidth.of(c) }

のはずである。
すると、実質的に str に対して二回 Unicode::DisplayWidth.of が呼ばれているってことになるな、と考える。
これ省けばそっちのが速いんじゃね?
けど、文字ごとにメソッドを呼ぶオーバーヘッドもあるし?

というわけで試してみる。 2 回呼んでるならメモ化すればいいじゃないの精神。

def truncate_string2(str, len, omission)
  widths = Hash.new{|h, k| h[k] = Unicode::DisplayWidth.of(k) }
  return str.dup unless str.each_char.inject(0){|r, c| r + widths[c] } > len
  lim = len - widths[omission]
  n = 0
  chars = str.each_char.take_while{|c| (n += widths[c]) <= lim }
  chars.join << omission
end

ベンチ。

                     user     system      total        real
ascii: no memo   2.309000   0.000000   2.309000 (  2.298512)
ascii: memo      0.562000   0.000000   0.562000 (  0.566717)
mb: no memo      0.999000   0.000000   0.999000 (  1.001561)
mb: memo         0.733000   0.000000   0.733000 (  0.725408)

効いてるっぽい。

さらによく考えると、 str を終わりまで舐めて全長を出すのは無駄である。 len を超えた時点で省略するのは確定するんだから。
というわけでほい。

def truncate_string3(str, len, omission)
  widths = Hash.new{|h, k| h[k] = Unicode::DisplayWidth.of(k) }
  n = 0
  over_len_p = false
  str.each_char do |c|
    if (n += widths[c]) > len
      over_len_p = true
      break
    end
  end
  return str.dup unless over_len_p
  lim = len - widths[omission]
  n = 0
  chars = str.each_char.take_while{|c| (n += widths[c]) <= lim }
  chars.join << omission
end

ベンチ。

                                 user     system      total        real
ascii: all len               0.577000   0.000000   0.577000 (  0.569295)
ascii: break when over len   0.374000   0.000000   0.374000 (  0.370433)
mb: all len                  0.734000   0.000000   0.734000 (  0.731004)
mb: break when over len      0.421000   0.000000   0.421000 (  0.416125)

これもまた効いてるかな。

とりあえず自分が使うのに問題はなさそうだから一旦満足。

Go 言語で String#eachや IO#eachみたいなことをしたい

忘れがちなので自分用メモ。

string に対して

package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    var src string = "foo\nbar\nbaz\n"
    var reader *strings.Reader = strings.NewReader(src)
    // strings.Reader は io.Reader を実装しているので…
    var scanner *bufio.Scanner = bufio.NewScanner(reader)

    for scanner.Scan() {
	fmt.Println(scanner.Text())
    }
}
実行結果
foo
bar
baz

File に対して

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    var scanner *bufio.Scanner = bufio.NewScanner(os.Stdin)

    for scanner.Scan() {
	fmt.Println(scanner.Text())
    }
    // エラー処理はこう。ループのあとでやる。
    if err := scanner.Err(); err != nil {
	fmt.Fprintln(os.Stderr, "Error: ", err)
    }
}
入力
hoge
piyo
fuga
実行結果
hoge
piyo
fuga

まとめ

io.Reader に仕立てあげて、そいつを bufio.Scanner にする。

tap break 教に入信しようと思う

便利。

たった一例しか使った記憶がないけど、それだけでも便利。

first_line = File.open("..."){|f|
  f.gets.tap{|line| break line.chomp if line }
}

tap なしだとださいローカル変数がががが

first_line = File.open("..."){|f|
  line = f.gets
  line ? line.chomp : line
}

line 、 line って何回言ってんだよ! スマホばっか見てる JK かよ!

( 1 回しか違わないんじゃないかな…)

class Object
  def tap_instance_eval(&block)
    tap{
      ret = instance_eval(&block)
      break ret unless equal?(ret)
    }
  end
end

first_line = File.open("..."){|f|
  f.gets.tap_instance_eval{ self and chomp }
}

ついに line が居なくなったぜ、ふっ。

(ここまでやったらやり過ぎなんだな…)

org-hatena-diary.el のテスト

org-mode -> はてな記法エクスポータも Emacs からはてなダイアリーの記事を投稿・管理するのも既にあったので手を
組ませてみる。

…実装の仕方に目を瞑りさすれば、なかなかよさげ?

設定例

(setq org-hatena-diary-username "アカウント名")
(setq org-hatena-diary-password "APIキー")

(global-set-key (kbd "<f12>") 'org-hatena-diary-list)

ライブラリ

;;; org-hatena-diary.el --- Write diary in org-mode and post it to hatena diary  -*- lexical-binding: t; -*-

;; Copyright (C) 2015  arikui

;; Author:  <arikui.ruby@gmail.com>
;; Keywords:

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see http://www.gnu.org/licenses/.

;;; Commentary:

;;  This elisp hacks two libraries,
;; hatena-diary.el(http://d.hatena.ne.jp/tarao/20130110/1357821338) and
;; ox-hatena.el(http://akisute3.hatenablog.com/entry/2013/08/23/150311),
;; to cooperate.

;;; Code:

(require 'hatena-diary)
(require 'ox-hatena)
(eval-when-compile (require 'cl))


(defun org-hatena-diary::advice-hatena-d-parse-content (f &rest args)
  (save-window-excursion
    (org-hatena-export-as-hatena)
    (apply f args)))

(advice-add 'hatena-d-parse-content :around 'org-hatena-diary::advice-hatena-d-parse-content)

(defun org-hatena-diary::turn-on-org-mode ()
  (when (string= "* Title" (buffer-string)) ; 記事の新規作成の場合
    (erase-buffer)
    (insert "#+TITLE: #+FILETAGS: ")
    (goto-char (point-min))
    (end-of-line))
  (org-mode))

(setq hatena:d:major-mode 'org-hatena-diary::turn-on-org-mode)

(defvaralias 'org-hatena-diary-username 'hatena:username)
(defvaralias 'org-hatena-diary-password 'hatena:password)

(defalias 'org-hatena-diary-list 'hatena:d:list)

(provide 'org-hatena-diary)
;;; org-hatena-diary.el ends here

Bison/Flex による再入可能字句/構文解析器

いろいろ試行錯誤があったので結論とかをまとめる。

使う API など

Flex

Flex が提供する

`--header-file' オプション
他のソースから yylex_init_extra() とかを使うため
YY_DECL マクロ
Bison が yylex() のシグネチャを決めつけてくるので歩み寄る
`%option reentrant' ディレクティブ
基本
yyget_extra() API
パーサオブジェクトを Flex 側から触る

電卓プログラムからの抜粋 - calc.lex:

  %{
  #include "./calc.tab.h"
  #define YY_DECL int yylex(YYSTYPE* yylval, void* yyscanner)

  #define PARSE_STATE yyget_extra(yyscanner)

  int yywrap(yyscan_t s){ return 1; }

  %}

  %option reentrant

  %%
   // ...
  %%
Bison 側
YYLEX_PARAM マクロ
パーサオブジェクトから yyscan_t 構造体を渡す用
`%pure_parser' ディレクティブ
基本
`%parse-param' ディレクティブ
yyparse() やら yyerror() やらにパーサオブジェクトを渡すため

電卓プログラムからの抜粋 - calc.y:

  %{
  #define YYDEBUG 1
  #include "./parse_state.h"

  #define YYLEX_PARAM p->scanner

  void yyerror(struct parse_state *p, const char *msg){
      parser_error(p, msg);
  }

  %}

  %pure_parser

  %parse-param { struct parse_state *p }

  /* other directives  */

  %%

  /* rules */

  %%

ハマったところ

Flex の出力したヘッダは、 Flex ソースでインクルードしてはいけない

なんかよく分かりませんが、内部用っぽいマクロをヘッダの最後で #undef しているので、まともに動かなくなります。

parse_state 構造体の定義のために yyscan_t を定義したヘッダが要るな -> parse_state.h で Flex ヘッダをインクルードする ->間接的に Flex ソースにもインクルードされる -> 動かない

結論を言うと、 yyscan_t は 単なる void* の typedef なので、 void* を使えばよいです。

電卓プログラムからの抜粋 - parse_state.h:

  #ifndef _PARSE_STATE_H_
  #define _PARSE_STATE_H_

  struct parse_state {
      void *scanner;
      /* ... 他のメンバ ... */
  };

  void parser_init(struct parse_state *);
  void parser_destroy(struct parse_state *);
  void parse(struct parse_state *);


  #endif /* _PARSE_STATE_H_ */

↑の関数の実装

  #include <stdio.h>
  #include "./parse_state.h"
  #include "./lex.yy.h"

  void parser_init(struct parse_state *p){
      yylex_init_extra(p, &(p->scanner));
      /* ... 他の初期化 ... */
  }

  void parser_destroy(struct parse_state *p){
      yylex_destroy(p->scanner);
      /* ... 他の後始末 ... */
  }

  void parse(struct parse_state *p){
      /* 必要に応じて yyset_in() などで設定 */
      if (yyparse(p) != 0) {
          fprintf(stderr, "error occured\n");
      }
  }
Bison の出力したヘッダは、 Bison ソースでインクルードしなくてもよい

トークンと %union の宣言だけで、しかも Bison の出力する .c にも含まれてる内容なので、インクルードしてもまったくの無駄。

YYPARSE_PARAM とかいうそれっぽいマクロの使いドコロ

最初は %parse-param ではなくこっちを使ってたのですが、 yyerror() とかの APIシグネチャにはまったくノータッチを決め込むので、無用の長物です。