Quantcast
Channel: Big Sky
Viewing all 121 articles
Browse latest View live

Pure Golang でスクリプト言語の VM 書いた。

$
0
0
あんこ

昨晩 lestrrat さんが go-nuts デビューした。
[ANN] Xslate template engine for golang
https://groups.google.com/d/msg/golang-nuts/bajxDXEsixk/OdRjXgkc5oYJ
lestrrat/go-xslate - GitHub

See Supported Syntax (TTerse) for what's currently available Debugging Currently the error reporting...

https://github.com/lestrrat/go-xslate
golang で書かれた xslate というテンプレートエンジンのポーティング。
Xslate - Scalable template engine for Perl5

Distributions Text::Clevery - Smarty compatible syntax and functions Text::Xslate::Bridge::Alloy - U...

http://xslate.org/
なんと現時点で html/templateよりも速いとの事。すばらしい。
テンプレートエンジンの中では小さな VM が動いてる。とてもカッコイイ。

それを聞いて、僕も言語処理系を書きたくなって、家に帰ってからもモヤモヤして寝れなかった。えいや!とばかりに僕も言語処理系を書いてみた。
mattn/anko - GitHub
https://github.com/mattn/anko
JavaScript っぽいような、Golang っぽい様な言語です。
以下の様なスクリプトが動きます。
# declare function
func foo(x){
  return x + 1;
}

# declare variables
var x = 1;
var y = x + 1;

# print values 
println(x * (y + 2 * x + foo(x) / 2));

# if/else condition
if (foo(y) > 1) {
  println("こんにちわ世界");
else {
  println("Hello, World");
}

# array type
var a = [1,2,3];
println(a[2]);
println(len(a));

# map type
var m = {"foo""bar""bar""baz"};
for k in keys(m) {
  println(m[k]);
}
勢いで書いたので、開発継続するかとか、この後どうするかとか、何も考えてません。現状、計算くらいしか出来ません。
ですが言語処理系を作る方法のサンプルとしては、誰かの役に立つかなーと思ったので公開しておきます。

追記
ちなみに anko という名前は、ふざけて「unko」にしようと思ったけど、いいのが思いつかずに「そう言えば昨晩、アンドーナツをアテに酒飲んでたなー」という安直な発想で付けました。

Golang で書いたスクリプト言語 Anko をウェブで試せるサイト作った。

$
0
0
先日、golang でスクリプト言語を書いた訳ですが
Big Sky :: Pure Golang でスクリプト言語の VM 書いた。
http://mattn.kaoriya.net/software/anko/20140328210749.htm
言語仕様もだいたい決まって、github で 70star 近く頂いてて、テストも書けてきて、ダンゴも表示出来て、そろそろ実用的になったんじゃないかと個人的に思ったりはしているのですが、誰もコントリビュートしてくれないという、悲しい状況が続いております。
あんまりなので、インストールしなくてもすぐさま試せる様に playground を作ってみました。
Anko Playground
http://play-anko.appspot.com/
golang の playground とほぼ同じです。(アニメーション機能等はありません)
これで誰でも Anko を試せる様になったのではないかと思います。

プログラミング言語の作り方

$
0
0
github の trending を見てたら面白い物を見つけた。
orangeduck/BuildYourOwnLisp - GitHub

Learn C and build your own programming language in under 1000 lines of code!

https://github.com/orangeduck/BuildYourOwnLisp
手順にそってC言語で lisp を実装する手順を見せるという物なのだが、その教材の一部としてパーサのコードが含まれている。 このパーサ部分だけ別のプロジェクトとして外出しされている。
orangeduck/mpc - GitHub

A Parser Combinator library for C

https://github.com/orangeduck/mpc
C言語で使えて汎用性のあるパーサライブラリだ。ライブラリとはいえ組み込み手順等は作られていないので、ビルドする場合はソースを一緒にコンパイルする形になるかと思います。
今日はこれを使って四則演算計算機を作ってみたいと思います。プログラミング言語を作った事のある方には釈迦に説法ですがご勘弁下さい。

mpc では YACC 構文を扱います。YACC 構文について詳しく知りたい方は Wikipedia等で調べて下さい。
本来であれば浮動小数も扱うべきですが今回は説明の為省きます。四則演算ですのでまず数値を定義します。mpc ではマッチングに正規表現が扱えます。
パーサライブラリによっては文字単位でしか扱えない物もあり自前でパースする必要もありますが、正規表現が使えるのはかなり便利です。
number : /-?[0-9]+/ ;
見ての通りですが、マイナス記号が0個もしくは1個あり、後続して数値の羅列があるという定義になります。
次にオペレータ部を定義します。計算式ですと
1 + 3 * 4
の様に複数の計算が混じります。そこで正規表現を用いて以下の様に定義します。
term : <number> (('*' | '/' | '+' | '-' | '%') <number>)* ;
正規表現の *を使います。これは計算式の左項(LHS)と、オペレータ記号および右項(RHS)の組み合わせが0個以上あるという定義になります。
しかしここで問題が発生します(わざとらしい)。四則演算では掛け算や割り算の方が優先されるのです。またカッコが付く事でその計算も優先される事になります。つまり足し算や引き算の定義よりも、掛け算や割り算、カッコの定義を優先する必要があるのです。
まず掛け算や割り算の定義をします。
term : <factor> (('*' | '/' | '%') <factor>)* ;
そしてこの factor (number ではありません) を定義します。factor には数値、もしくはカッコで囲まれた式が入ります。
ですので factor の定義は以下の様になります。
factor : '('<lexp> ')'
       | <number> ;
このカッコの中身 lexp は、term を先に計算した結果に対して足し算や引き算を実行する定義を書けば良いので以下の様に定義出来ます。
lexp : <term> (('+' | '-') <term>)* ;
まとめると
number    : /-?[0-9]+/ ;
factor    : '('<lexp> ')'
          | <number> ;

term      : <factor> (('*' | '/' | '%') <factor>)* ;
lexp      : <term> (('+' | '-') <term>)* ;
この様になります。循環していますが、この辺はパーサライブラリが宜しくやってくれます。

言語の処理系では上記 number や factor 等の単位で処理を行います。この単位の構造を作ってくれるのがパーサという物で、パーサが作るこの木構造を AST (抽象構文木) と言います。
さて定義が出来たので mpc に食わせましょう。木構造のノード定義となる number や factor を作ります。
mpc_parser_t* Number = mpc_new("number");
mpc_parser_t* Factor = mpc_new("factor");
mpc_parser_t* Term   = mpc_new("term");
mpc_parser_t* Lexp   = mpc_new("lexp");
そしてこれを定義の順通りに mpca_lang に渡します。この辺はパーサライブラリの使い方によって様々ですのでご注意下さい。全体のコードだと以下の様になります。
#include "../mpc.h"

#define STRUCTURE \
"                                                         \n" \
"  number    : /-?[0-9]+/ ;                               \n" \
"  factor    : '('<lexp> ')'                             \n" \
"            | <number> ;                                 \n" \
"                                                         \n" \
"  term      : <factor> (('*' | '/' | '%') <factor>)* ;   \n" \
"  lexp      : <term> (('+' | '-') <term>)* ;             \n"

int main(int argc, char **argv) {
  if (argc != 2) {
    fprintf(stderr"usage of %s: expr", argv[0]);
    exit(0);
  }
  mpc_result_t result;
  mpc_parser_t* Number    = mpc_new("number");
  mpc_parser_t* Factor    = mpc_new("factor");
  mpc_parser_t* Term      = mpc_new("term");
  mpc_parser_t* Lexp      = mpc_new("lexp");

  mpc_err_t* err = mpca_lang(MPC_LANG_DEFAULT, STRUCTURE, Number, Factor , Term, Lexp);
  if (err != NULL) {
    mpc_err_print(err);
    mpc_err_delete(err);
    goto leave;
  }

  if (!mpc_parse("<argument>", argv[1], Lexp, &result)) {
    mpc_err_print(result.error);
    mpc_err_delete(result.error);
    goto leave;
  }

  mpc_ast_print(result.output);
  mpc_ast_delete(result.output);

leave:
  mpc_cleanup(4, Number, Factor, Term, Lexp);
  
  return 0;
  
}
このプログラムは、引数で与えられた文字列を今回定義した四則演算の定義を元にパースし、出来上がった AST を表示する物です。
試しに 3 * (1 + 2)という引数を与えると以下の様な出力が得られます。
>:
  term|>:
    factor|number|regex: '3'
    char: '*'
    factor|>:
      char: '('
      lexp|>:
        term|factor|number|regex: '1'
        char: '+'
        term|factor|number|regex: '2'
      char: ')'
ちゃんと動いてますね。ここまで出来たら計算処理部分を作ります。これが世に言う VM (仮想機械) と呼ばれる部分です。mpc の AST は以下のコードで表現されます。
typedef struct mpc_ast_t {
  char *tag;
  char *contents;
  int children_num;
  struct mpc_ast_t** children;
} mpc_ast_t;
ノード名は tag という文字列型で表現されています。セパレータは |終端は :の様です。今回ですとノード名はほぼユニークですので、簡略化の為に strstr を使います。
まず数値。number は上記の定義でマッチした物そのものなので contents というメンバに入ります。今回は整数だけ扱うので atoi で。
int eval(mpc_ast_t* t) {
  if (is_a(t, "number")) {
    return atoi(t->contents);
  }
  return 0;
}
次に factor。factor の定義で lexp は2番目の枝になるので
#define is_a(t, a) (strstr(t->tag, a) != NULL)

int eval(mpc_ast_t* t) {
  if (is_a(t, "number")) {
    return atoi(t->contents);
  }
  if (is_a(t, "factor")) {
    return eval(t->children[1]);
  }
  return 0;
}
2番目のノードを得て再起呼び出しとしました。ここを再起を使わずどう実装するかで言語処理系の高速さが決まったりします。
さて最後に term と lexp ですが、どちらも左項(LHS)と、オペレータおよび右項(RHS)の組み合わせを繰り返した物、という定義ですので
#define is_a(t, a) (strstr(t->tag, a) != NULL)

int eval(mpc_ast_t* t) {
  if (is_a(t, "number")) {
    return atoi(t->contents);
  }
  if (is_a(t, "factor")) {
    return eval(t->children[1]);
  }
  if (t->children_num >= 1) {
    int x = eval(t->children[0]), i;
    for (i = 1; i < t->children_num; i += 2) {
      char* op = t->children[i]->contents;
      int rhs = eval(t->children[i+1]);
      if (strcmp(op, "+") == 0) { x += rhs; }
      if (strcmp(op, "-") == 0) { x -= rhs; }
      if (strcmp(op, "*") == 0) { x *= rhs; }
      if (strcmp(op, "/") == 0) { x /= rhs; }
      if (strcmp(op, "%") == 0) { x %= rhs; }
    }
    return x;
  }
  return 0;
}
この様なコードになりました。
本当は知らないノードが来たらエラーにする等の処理が必要ですが、簡略化の為省きます。
あとはこれを呼び出します。全体のコードを掲載しておきます。
#include "../mpc.h"

#define STRUCTURE \
"                                                         \n" \
"  number    : /-?[0-9]+/ ;                               \n" \
"  factor    : '('<lexp> ')'                             \n" \
"            | <number> ;                                 \n" \
"                                                         \n" \
"  term      : <factor> (('*' | '/' | '%') <factor>)* ;   \n" \
"  lexp      : <term> (('+' | '-') <term>)* ;             \n"

#define is_a(t, a) (strstr(t->tag, a) != NULL)

int eval(mpc_ast_t* t) {
  if (is_a(t, "number")) {
    return atoi(t->contents);
  }
  if (is_a(t, "factor")) {
    return eval(t->children[1]);
  }
  if (t->children_num >= 1) {
    int x = eval(t->children[0]), i;
    for (i = 1; i < t->children_num; i += 2) {
      char* op = t->children[i]->contents;
      int rhs = eval(t->children[i+1]);
      if (strcmp(op, "+") == 0) { x += rhs; }
      if (strcmp(op, "-") == 0) { x -= rhs; }
      if (strcmp(op, "*") == 0) { x *= rhs; }
      if (strcmp(op, "/") == 0) { x /= rhs; }
      if (strcmp(op, "%") == 0) { x %= rhs; }
    }
    return x;
  }
  return 0;
}

int main(int argc, char **argv) {
  if (argc != 2) {
    fprintf(stderr"usage of %s: expr", argv[0]);
    exit(0);
  }
  mpc_result_t result;
  mpc_parser_t* Number    = mpc_new("number");
  mpc_parser_t* Factor    = mpc_new("factor");
  mpc_parser_t* Term      = mpc_new("term");
  mpc_parser_t* Lexp      = mpc_new("lexp");

  mpc_err_t* err = mpca_lang(MPC_LANG_DEFAULT, STRUCTURE, Number, Factor , Term, Lexp);
  if (err != NULL) {
    mpc_err_print(err);
    mpc_err_delete(err);
    goto leave;
  }

  if (!mpc_parse("<argument>", argv[1], Lexp, &result)) {
    mpc_err_print(result.error);
    mpc_err_delete(result.error);
    goto leave;
  }

  //mpc_ast_print(result.output);
  printf("%d\n", eval(result.output));
  mpc_ast_delete(result.output);

leave:
  mpc_cleanup(4, Number, Factor, Term, Lexp);
  
  return 0;
  
}
コンパイルは前述の通り mpc.cをコンパイルリンクします。試しに実行すると
$ calc "1"
1

$ calc "1+2"
3

$ calc "3 * (1 + 2)"
9

$ calc "3 * (1 + 4 / 2)"
9
おー!ちゃんと動いています。

今回は四則演算計算機を作りましたが、例えば定義でアルファベットから始まる単語を以下の様に定義し
ident : /[a-zA-Z][a-zA-Z0-9_]+/ ;
ident というタグが来たらメモリ内にある辞書からそれをキーに検索して数値を返す、という処理を書いたとしたらどうなりますか?

へっ... 変数!


そうです。変数です。代入こそは出来ませんがプログラミング言語の入り口に一歩足を入れた感じがしますね。
この上記の定義および実装を繰り返し
  • 変数定義
  • 変数参照
  • 変数代入
  • 条件分岐
  • 反復処理
  • 関数定義
  • 関数呼出
などを実装するとプログラミング言語になっていくわけです。あの Ruby もこの様な手続きで開発されています。
どうですか?プログラミング言語、作ってみたくなりませんか!
今回は mpc をサンプルとして使いましたが世の中には bison 等の有名な yacc 定義ツールがあります。皆さんもぜひ面白い物を作ってみて下さい。

プログラミング言語の作り方(2)

$
0
0
この前の記事がなかなか人気があったので、続きを書いてみます。

Big Sky :: プログラミング言語の作り方

github の trending を見てたら面白い物を見つけた。 orangeduck/BuildYourOwnLisp - GitHub Learn C and build your own pr...

http://mattn.kaoriya.net/software/build_your_own_programming_language.htm
前回の記事では単なる四則演算しか出来ないし、「どこがプログラミング言語やねん」と言われかねないのでもう少しやってみます。 そしてどうやったらプログラミング言語っぽくなるのかを書いてみたいと思います。

前回の記事後半で変数について書きました。変数は ident とう識別で以下の様に定義します。
ident : /[a-zA-Z][a-zA-Z0-9_]*/ ;
ここで言語のシンタックス設計が必要になります。このサンプルでは以下の様なシンタックスにします。
まず代入
a = 1;
b = "hello world";
計算
a = a * 2 + 1;
表示
println(a + 1);
この単位がプログラミング言語でいう statement(文) になります。また前回に作った要素は statement ではなく expression(式) となります。代入は ident が左項、右項が lexp になるので以下の様に定義出来ます。
let : <ident> '='<lexp> ';' ;
ついでなので値を表示する関数を実装します。関数は以下の様に定義出来ます。
call : <ident> '('<lexp>? (','<lexp>)* ')'';' ;
最後に let と call の2つの命令が並んでいるという定義を書きます。全体は以下の通り。
number    : /-?[0-9]+/ ;
factor    : '('<lexp> ')'
          | <number>
          | <string>
          | <ident> ;
string    : /"[^\"]*"/ ;
ident     : /[a-zA-Z][a-zA-Z0-9_]*/ ;

term      : <factor> (('*' | '/' | '%') <factor>)* ;
lexp      : <term> (('+' | '-') <term>)* ;
let       : <ident> '='<lexp> ';' ;
call      : <ident> '('<lexp>? (','<lexp>)* ')'';' ;
stmts     : (<let> | <call>)* ;
なお文字列は本来ならばリテラルエスケープも処理すべきですが省略します。
この stmts が処理すべき命令一覧になります。前回同様に、識別を mpc_new で作り parse 関数に渡します。ただしそろそろ引数ではしんどくなってきたので mpc_parse_contents を使い、ファイル名を受け取ります。
int main(int argc, char **argv) {
  if (argc != 2) {
    fprintf(stderr"usage of %s: file", argv[0]);
    exit(0);
  }
  mpc_result_t result;
  mpc_parser_t* Number = mpc_new("number");
  mpc_parser_t* Factor = mpc_new("factor");
  mpc_parser_t* String = mpc_new("string");
  mpc_parser_t* Ident  = mpc_new("ident");
  mpc_parser_t* Term   = mpc_new("term");
  mpc_parser_t* Lexp   = mpc_new("lexp");
  mpc_parser_t* Let    = mpc_new("let");
  mpc_parser_t* Call   = mpc_new("call");
  mpc_parser_t* Stmts  = mpc_new("stmts");

  mpc_err_t* err = mpca_lang(MPC_LANG_DEFAULT, STRUCTURE,
      Number, Factor, String, Ident, Term, Lexp, Let, Call, Stmts);
  if (err != NULL) {
    mpc_err_print(err);
    mpc_err_delete(err);
    goto leave;
  }

  if (!mpc_parse_contents(argv[1], Stmts, &result)) {
    mpc_err_print(result.error);
    mpc_err_delete(result.error);
    goto leave;
  }

  //mpc_ast_print(result.output);
  eval(result.output);
  mpc_ast_delete(result.output);

leave:
  mpc_cleanup(9,
      Number, Factor, String, Ident, Term, Lexp, Let, Call, Stmts);
  return 0;
}
変数は、あるスコープ(今回だと全部グローバル)で見た時にある識別で認識出来る物がキーとなるので変数格納領域は hash を使います。この変数格納方法は言語処理系によって分かれます。GC 管理しやすい様に管理したりもします。今回のプログラムでは GC は扱ったりはしません。まず変数には文字列もしくは数値が格納されるので以下の値型を用意しました。
#define TYPE_NIL 0
#define TYPE_NUM 1
#define TYPE_STR 2

typedef struct {
  int t;
  union _v {
    int i;
    char* s;
  } v;
} V;

V V_NIL = { TYPE_NIL, 0 };
t に型識別、v の中の i もしくは s に値が入ります。そしてそれを格納する hash を定義します。hash の実装には mruby も使っている khash を使いました。
attractivechaos/klib ・ GitHub
https://github.com/attractivechaos/klib
なお、文字列はリテラルからダブルクォートを取り除いた物をメモリ確保して格納するので、GC とまでは行きませんが解放用に list で保持しておきます。こちらも khash と同じリポジトリに入っている klist を使います。
KHASH_MAP_INIT_STR(ident, V)
khash_t(ident) *env;

void v_free(void *p) {
  V* v = (V*) p;
  if (v->t == TYPE_STR)
    free(v->v.s);
}
KLIST_INIT(ident, V, v_free)
klist_t(ident) *gc;
ident 識別を認識したらこの hash から ident をキーとして実値を取り出します。
if (is_a(t, "ident")) {
  khint_t k = kh_get(ident, env, t->contents);
  if (k == kh_end(env)) {
    return V_NIL;
  }
  return kh_value(env, k);
}
そして代入部分は hash への push を行います。
if (is_a(t, "let")) {
  int r = 0;
  V v;
  khint_t k = kh_put(ident, env, t->children[0]->contents, &r);
  v = eval(t->children[2]);
  kh_value(env, k) = v;
  return v;
}
全体のコードだと以下の様になります。今回はコンパイルに mpc.h mpc.c だけでなく khash.h と klist.h も必要になります。
#include "mpc.h"
#include "khash.h"
#include "klist.h"

#define STRUCTURE \
"                                                            \n" \
"  number    : /-?[0-9]+/ ;                                  \n" \
"  factor    : '('<lexp> ')'                                \n" \
"            | <number>                                      \n" \
"            | <string>                                      \n" \
"            | <ident> ;                                     \n" \
"  string    : /\"[^\"]*\"/ ;                                \n" \
"  ident     : /[a-zA-Z][a-zA-Z0-9_]*/ ;                     \n" \
"                                                            \n" \
"  term      : <factor> (('*' | '/' | '%') <factor>)* ;      \n" \
"  lexp      : <term> (('+' | '-') <term>)* ;                \n" \
"  let       : <ident> '='<lexp> ';' ;                      \n" \
"  call      : <ident> '('<lexp>? (','<lexp>)* ')'';' ;   \n" \
"  stmts     : (<let> | <call>)* ;                           \n" 

#define is_a(t, a) (strstr(t->tag, a) != NULL)

#define TYPE_NIL 0
#define TYPE_NUM 1
#define TYPE_STR 2

typedef struct {
  int t;
  union _v {
    int i;
    char* s;
  } v;
} V;

V V_NIL = { TYPE_NIL, 0 };

KHASH_MAP_INIT_STR(ident, V)
khash_t(ident) *env;

void v_free(void *p) {
  V* v = (V*) p;
  if (v->t == TYPE_STR)
    free(v->v.s);
}
KLIST_INIT(ident, V, v_free)
klist_t(ident) *gc;

V eval(mpc_ast_t* t) {
  int i;
  if (is_a(t, "number")) {
    V v = { TYPE_NUM };
    v.v.i = atoi(t->contents);
    return v;
  }
  if (is_a(t, "string")) {
    V v = { TYPE_STR };
    size_t l = strlen(t->contents) - 2;
    v.v.s = calloc(1, l + 1);
    strncpy(v.v.s, t->contents + 1, l);
    v.v.s[l] = 0;
    *kl_pushp(ident, gc) = v;
    return v;
  }
  if (is_a(t, "ident")) {
    khint_t k = kh_get(ident, env, t->contents);
    if (k == kh_end(env)) {
      return V_NIL;
    }
    return kh_value(env, k);
  }
  if (is_a(t, "factor")) {
    return eval(t->children[1]);
  }
  if (is_a(t, "lexp") || is_a(t, "term")) {
    V lhs = eval(t->children[0]);
    for (i = 1; i < t->children_num; i += 2) {
      char* op = t->children[i]->contents;
      V rhs = eval(t->children[i+1]);
      int iv = rhs.t == TYPE_NUM ? rhs.v.i : 0;
      if (strcmp(op, "+") == 0) { lhs.v.i += iv; }
      if (strcmp(op, "-") == 0) { lhs.v.i -= iv; }
      if (strcmp(op, "*") == 0) { lhs.v.i *= iv; }
      if (strcmp(op, "/") == 0) { lhs.v.i /= iv; }
      if (strcmp(op, "%") == 0) { lhs.v.i %= iv; }
    }
    return lhs;
  }
  if (is_a(t, "let")) {
    int r = 0;
    V v;
    khint_t k = kh_put(ident, env, t->children[0]->contents, &r);
    v = eval(t->children[2]);
    kh_value(env, k) = v;
    return v;
  }
  if (is_a(t, "call")) {
    int r = 0, v;
    if (!strcmp(t->children[0]->contents, "println")) {
      for (i = 2; i < t->children_num - 2; i += 2) {
        if (i != 2) printf(", ");
        V v = eval(t->children[i]);
        switch (v.t) {
          case TYPE_NIL:
            printf("nil");
            break;
          case TYPE_NUM:
            printf("%d", v.v.i);
            break;
          case TYPE_STR:
            printf("%s", v.v.s);
            break;
        }
      }
      printf("\n");
    } else {
      fprintf(stderr"Unknwn function '%s'\n", t->children[0]->contents);
    }
    return V_NIL;
  }
  for (i = 0; i < t->children_num; i++) {
    eval(t->children[i]);
  }
  return V_NIL;
}

int main(int argc, char **argv) {
  if (argc != 2) {
    fprintf(stderr"usage of %s: file", argv[0]);
    exit(0);
  }
  mpc_result_t result;
  mpc_parser_t* Number = mpc_new("number");
  mpc_parser_t* Factor = mpc_new("factor");
  mpc_parser_t* String = mpc_new("string");
  mpc_parser_t* Ident  = mpc_new("ident");
  mpc_parser_t* Term   = mpc_new("term");
  mpc_parser_t* Lexp   = mpc_new("lexp");
  mpc_parser_t* Let    = mpc_new("let");
  mpc_parser_t* Call   = mpc_new("call");
  mpc_parser_t* Stmts  = mpc_new("stmts");

  mpc_err_t* err = mpca_lang(MPC_LANG_DEFAULT, STRUCTURE,
      Number, Factor, String, Ident, Term, Lexp, Let, Call, Stmts);
  if (err != NULL) {
    mpc_err_print(err);
    mpc_err_delete(err);
    goto leave;
  }

  env = kh_init(ident);
  gc = kl_init(ident);

  if (!mpc_parse_contents(argv[1], Stmts, &result)) {
    mpc_err_print(result.error);
    mpc_err_delete(result.error);
    goto leave;
  }

  mpc_ast_print(result.output);
  eval(result.output);
  mpc_ast_delete(result.output);
  kl_destroy(ident, gc);

leave:
  mpc_cleanup(9,
      Number, Factor, String, Ident, Term, Lexp, Let, Call, Stmts);
  return 0;
}
第一引数に渡すファイルを作ります。
a = 1;
a = a * 2 + 1;
b = "hello";
println(ba + 1);
実行すると...
hello, 4
動きました。とりあえず
  • 変数代入
  • 変数参照
  • 関数呼出
は出来ました。計算しか出来ないプログラムですが、プログラミング言語の作り方をイメージして頂けるのではと思います。
ここら辺からだんだんしんどくなってきますが、自分で作ったプログラミング言語は愛着もあり将来残って行く物になると思います。

プログラミング言語の作り方(3)

$
0
0
もうちょっと続けてみようと思います。
Big Sky :: プログラミング言語の作り方

github の trending を見てたら面白い物を見つけた。 orangeduck/BuildYourOwnLisp - GitHub Learn C and build your own pr...

http://mattn.kaoriya.net/software/build_your_own_programming_language.htm
Big Sky :: プログラミング言語の作り方(2)

この前の記事がなかなか人気があったので、続きを書いてみます。 Big Sky :: プログラミング言語の作り方 github の trending を見てたら面白い物を見つけた。 orangeduck...

http://mattn.kaoriya.net/software/build_your_own_programming_language2.htm
言語名を決めました。「俺言語」です。
mattn/orelang - GitHub

俺言語

https://github.com/mattn/orelang/
今回は今後を見据えてコードのブラッシュアップを行いたいと思います。

軽微な修正

前回までのコードですと、ステートメントが EOF で終了するという定義が出来ていなかったので、途中でパースエラーが起きても正常に動作してしまっていました。そこで一番外側を stmts ではなく program という識別にして「ステートメントの繰り返しがあり、最後に EOF が来る」という定義にしました。オマケで comment も足しておきました。
comment : /#[^\n]*/ ;
eof     : /$/ ;
stmt    : (<let> | <call> | <comment>) ;
program : <stmt>* <eof> ;
これで俺言語の中でコメントが打てる様になります。

リファクタリング

次に言語も決まった事なのでリファクタリングを行いました。
全ての関数に oreプレフィックスを付けました。この辺は好き好きですが付けておくと愛着が湧いたりします(どうでもいいですね)。
また今回の修正で扱える様にする型を増やして以下の様に ore_value (値を格納する構造体) を定義しました。
typedef enum {
  ORE_TYPE_NIL,
  ORE_TYPE_INT,
  ORE_TYPE_FLOAT,
  ORE_TYPE_STR,
  ORE_TYPE_FUNC,
  ORE_TYPE_CFUNC
} ore_type;

typedef mpc_ast_t* ore_func;

typedef struct _ore_value {
  ore_type t;
  union _v {
    int i;
    double d;
    char* s;
    void* c;
    ore_func f;
  } v;
} ore_value;
ORE_TYPE_CFUNC は前回追加した println の様な「C言語側の関数」を、ORE_TYPE_FUNC は俺言語内で定義した関数を格納します。
便利関数 ore_define_cfunc を用意して、println の様なコア関数の登録を簡単に出来る様にしました。
ore_value
ore_println(ore_context* ore, int num_in, ore_value* args) {
  int i;
  for (i = 0; i < num_in; i++) {
    if (i != 0) printf(", ");
    switch (args[i].t) {
      case ORE_TYPE_NIL:
        printf("nil");
        break;
      case ORE_TYPE_INT:
        printf("%d", args[i].v.i);
        break;
      case ORE_TYPE_FLOAT:
        printf("%f", args[i].v.d);
        break;
      case ORE_TYPE_STR:
        printf("%s", args[i].v.s);
        break;
      default:
        printf("<unkonwn>");
        break;
    }
  }
  printf("\n");
  return ore_value_nil();
}
この様な関数シグネチャにしておき、以下の様に登録します。
ore_context* ore = ore_new(NULL);
ore_define_cfunc(ore, "println", ore_println);
ore_eval(ore, result.output);
ore_destroy(ore);
今後、配列をサポートした際の len()関数等も簡単に作成出来る様になりました。

今後の目論見

上記で説明した ore_println の第一引数に ore_context というのが出てきました。ore_context 構造体は以下の通り。
<span class="Type">typedef</span>&#160;<span class="Type">struct</span>&#160;_ore_context {<br />
khash_t(ident)* env;<br />
klist_t(ident)* gc;<br />
<span class="Type">struct</span>&#160;_ore_context* parent;<br />
} ore_context;<br />
前回グローバル変数で宣言していた env と gc を構造体内部に押しやりました。単純にかっこいいからとかではなく、こうしたのにはちゃんと理由があります。この env は ident で示す識別に対して値を格納する物、つまり「スコープ」の役割を果たします。今後、関数宣言を追加しますがその際にスコープを拘束する必要があるのです。また関数が終了するとメモリの解放が必要になります。
プログラミング言語のスコープには大きく分けて2つあります。
  • ダイナミックスコープ
  • レキシカルスコープ
俺言語ではレキシカルスコープを採用する予定なので、関数宣言とその宣言時の環境を抱合せて保持する必要があります。理由は以下のJavaScriptのコードで分かるかと思います。
var count = function() {
  var c = 1;
  return function() {
    return c++;
  };
}();

console.log(count()); // 1
console.log(count()); // 2
まず cが宣言されるのは関数スコープとなります。また cの宣言はグローバルスコープを汚す物であってはなりません。
また ccountが保持する環境を参照してインクリメンタルする必要があります。
上記、ore_context 構造体で parent を保持しているのにも理由があります。
例えば
c = 1;
と実行される場合、以下の処理を行う必要があります。
  • 現在のスコープからグローバルスコープに向かって cという識別で検索し、見つかれば値を 1 に更新する。
  • どこにもなければグローバルスコープに cという識別で値 1 を保存する。
その為、現在のスコープからグローバルスコープに向かって探索出来る様に parent が必要となります。今回は対応しませんが今後、上記の関数スコープを実装します。その為の準備となります。

浮動小数点のサポート

上記で ore_value に double 型を追加しています。そこで number 識別を認識した際のパース処理を以下の様に修正します。
ore_value
ore_parse_num(ore_context* ore, const char* s) {
  ore_value v;
  if (!strchr(s, '.')) {
    v.t = ORE_TYPE_INT;
    v.v.i = atoi(s);
  } else {
    v.t = ORE_TYPE_FLOAT;
    v.v.d = atof(s);
  }
  return v;
}
横着感が満載ですね。同様に演算部分も修正します。
if (is_a(t, "lexp") || is_a(t, "term")) {
  v = ore_eval(ore, t->children[0]);
  for (i = 1; i < t->children_num; i += 2) {
    char* op = t->children[i]->contents;
    ore_value rhs = ore_eval(ore, t->children[i+1]);
    switch (v.t) {
      case ORE_TYPE_INT:
        {
          int iv = rhs.t == ORE_TYPE_INT ? rhs.v.i : rhs.t == ORE_TYPE_FLOAT ? (int) rhs.v.d : 0;
          if (strcmp(op, "+") == 0) { v.v.i += iv; }
          if (strcmp(op, "-") == 0) { v.v.i -= iv; }
          if (strcmp(op, "*") == 0) { v.v.i *= iv; }
          if (strcmp(op, "/") == 0) { v.v.i /= iv; }
          if (strcmp(op, "%") == 0) { v.v.i %= iv; }
        }
        break;
      case ORE_TYPE_FLOAT:
        {
          double fv = rhs.t == ORE_TYPE_INT ? (double) rhs.v.i : rhs.t == ORE_TYPE_FLOAT ? rhs.v.d : 0;
          if (strcmp(op, "+") == 0) { v.v.d += fv; }
          if (strcmp(op, "-") == 0) { v.v.d -= fv; }
          if (strcmp(op, "*") == 0) { v.v.d *= fv; }
          if (strcmp(op, "/") == 0) { v.v.d /= fv; }
          if (strcmp(op, "%") == 0) { v.v.d = ((int) v.v.d % (int) fv); }
        }
        break;
    }
  }
  return v;
}
今後、文字列の足し算で文字列結合する処理も必要になりますね。

関数宣言

今回の修正で yacc 構文を以下の様にしました。
#define STRUCTURE \
"                                                           \n" \
"number    : /-?[0-9]+(\\.[0-9]*)?(e[0-9]+)?/ ;             \n" \
"factor    : '('<lexp> ')'                                 \n" \
"          | <number>                                       \n" \
"          | <string>                                       \n" \
"          | <lambda>                                       \n" \
"          | <ident> ;                                      \n" \
"string    : /\"[^\"]*\"/ ;                                 \n" \
"ident     : /[a-zA-Z][a-zA-Z0-9_]*/ ;                      \n" \
"                                                           \n" \
"term      : <factor> (('*' | '/' | '%') <factor>)* ;       \n" \
"lexp      : <term> (('+' | '-') <term>)* ;                 \n" \
"let       : <ident> '='<lexp> ';' ;                       \n" \
"                                                           \n" \
"lambda    : /func/                                           " \
"        '('<ident>? (','<ident>)* ')''{'<stmt>* '}' ;  \n" \
"func      : /func/ <ident>                                   " \
"        '('<ident>? (','<ident>)* ')''{'<stmt>* '}' ;  \n" \
"                                                           \n" \
"call      : <ident> '('<lexp>? (','<lexp>)* ')'';' ;    \n" \
"comment   : /#[^\n]*/ ;                                    \n" \
"eof       : /$/ ;                                          \n" \
"stmt      : (<let> | <call> | <func> | <comment>) ;        \n" \
"program   : <stmt>* <eof> ;                                \n"
factor に lambda を、stmt に func を追加しました。引数部は identの羅列を受け取ります。
今回は実装していないので引数の受け渡しは動作しませんが、今後「関数スコープ」が実装された際は、関数呼び出し時に新規に env を作成し、ident が示す変数名を登録してあげれば引数が渡される事になります。
最後に全体のコードを載せて置きます。
#include "mpc.h"
#include "khash.h"
#include "klist.h"

#define STRUCTURE \
"                                                           \n" \
"number    : /-?[0-9]+(\\.[0-9]*)?(e[0-9]+)?/ ;             \n" \
"factor    : '('<lexp> ')'                                 \n" \
"          | <number>                                       \n" \
"          | <string>                                       \n" \
"          | <lambda>                                       \n" \
"          | <ident> ;                                      \n" \
"string    : /\"[^\"]*\"/ ;                                 \n" \
"ident     : /[a-zA-Z][a-zA-Z0-9_]*/ ;                      \n" \
"                                                           \n" \
"term      : <factor> (('*' | '/' | '%') <factor>)* ;       \n" \
"lexp      : <term> (('+' | '-') <term>)* ;                 \n" \
"let       : <ident> '='<lexp> ';' ;                       \n" \
"                                                           \n" \
"lambda    : /func/                                           " \
"        '('<ident>? (','<ident>)* ')''{'<stmt>* '}' ;  \n" \
"func      : /func/ <ident>                                   " \
"        '('<ident>? (','<ident>)* ')''{'<stmt>* '}' ;  \n" \
"                                                           \n" \
"call      : <ident> '('<lexp>? (','<lexp>)* ')'';' ;    \n" \
"comment   : /#[^\n]*/ ;                                    \n" \
"eof       : /$/ ;                                          \n" \
"stmt      : (<let> | <call> | <func> | <comment>) ;        \n" \
"program   : <stmt>* <eof> ;                                \n"

void ore_free(void *p);

#define is_a(t, a) (strstr(t->tag, a) != NULL)

typedef enum {
  ORE_TYPE_NIL,
  ORE_TYPE_INT,
  ORE_TYPE_FLOAT,
  ORE_TYPE_STR,
  ORE_TYPE_FUNC,
  ORE_TYPE_CFUNC
} ore_type;

typedef mpc_ast_t* ore_func;

typedef struct _ore_value {
  ore_type t;
  union _v {
    int i;
    double d;
    char* s;
    void* c;
    ore_func f;
  } v;
} ore_value;

KHASH_MAP_INIT_STR(ident, ore_value)

KLIST_INIT(ident, ore_value, ore_free)

typedef struct _ore_context {
  khash_t(ident)* env;
  klist_t(ident)* gc;
  struct _ore_context* parent;
} ore_context;

typedef ore_value (*ore_cfunc_t)(ore_context*, int, ore_value*);

ore_value ore_call(ore_context*, mpc_ast_t*);
ore_value ore_eval(ore_context*, mpc_ast_t*);

void
ore_free(void *p) {
  ore_value* v = (ore_value*) p;
  switch (v->t) {
    case ORE_TYPE_STR:
      free(v->v.s);
      break;
    case ORE_TYPE_FUNC:
      free(v->v.c);
      break;
  }
}

ore_value
ore_value_nil() {
  ore_value v = { ORE_TYPE_NIL, 0 };
  return v;
}

ore_value
ore_parse_num(ore_context* ore, const char* s) {
  ore_value v;
  if (!strchr(s, '.')) {
    v.t = ORE_TYPE_INT;
    v.v.i = atoi(s);
  } else {
    v.t = ORE_TYPE_FLOAT;
    v.v.d = atof(s);
  }
  return v;
}

ore_value
ore_parse_str(ore_context* ore, const char* s) {
  ore_value v = { ORE_TYPE_STR };
  size_t l = strlen(s) - 2;
  v.v.s = calloc(1, l + 1);
  strncpy(v.v.s, s + 1, l);
  v.v.s[l] = 0;
  *kl_pushp(ident, ore->gc) = v;
  return v;
}

ore_value
ore_println(ore_context* ore, int num_in, ore_value* args) {
  int i;
  for (i = 0; i < num_in; i++) {
    if (i != 0) printf(", ");
    switch (args[i].t) {
      case ORE_TYPE_NIL:
        printf("nil");
        break;
      case ORE_TYPE_INT:
        printf("%d", args[i].v.i);
        break;
      case ORE_TYPE_FLOAT:
        printf("%f", args[i].v.d);
        break;
      case ORE_TYPE_STR:
        printf("%s", args[i].v.s);
        break;
      default:
        printf("<unkonwn>");
        break;
    }
  }
  printf("\n");
  return ore_value_nil();
}

ore_value
ore_define_cfunc(ore_context* ore, const char* name, ore_cfunc_t c) {
  int r = 0;
  khint_t k = kh_put(ident, ore->env, name, &r);
  ore_value v = { ORE_TYPE_CFUNC };
  v.v.c = c;
  kh_value(ore->env, k) = v;
  return v;
}

ore_value
ore_call(ore_context* ore, mpc_ast_t *t) {
  khint_t k = kh_get(ident, ore->env, t->children[0]->contents);
  if (k == kh_end(ore->env)) {
    fprintf(stderr"Unknwn function '%s'\n", t->children[0]->contents);
    return ore_value_nil();
  }

  ore_value fn = kh_value(ore->env, k);
  ore_value v = ore_value_nil();
  switch (fn.t) {
    case ORE_TYPE_CFUNC:
      {
        int num_in = t->children_num / 2 - 1, n = 0, i;
        ore_value* args = (ore_value*) malloc(sizeof(ore_value) * num_in);
        for (i = 2; i < t->children_num - 2; i += 2) {
          args[n++] = ore_eval(ore, t->children[i]);
        }
        v = ((ore_cfunc_t)fn.v.c) (ore, num_in, args);
        free(args);
      }
      break;
    case ORE_TYPE_FUNC:
      // TODO: ここをちゃんと実装して引数を渡せる様にする
      v = ore_eval(ore, fn.v.f);
      break;
  }
  return v;
}

ore_value
ore_eval(ore_context* ore, mpc_ast_t* t) {
  int i, r;
  ore_value v;
  if (is_a(t, "eof") || is_a(t, "comment")) {
    return ore_value_nil();
  }
  if (is_a(t, "number")) {
    return ore_parse_num(ore, t->contents);
  }
  if (is_a(t, "string")) {
    return ore_parse_str(ore, t->contents);
  }
  if (is_a(t, "ident")) {
    khint_t k = kh_get(ident, ore->env, t->contents);
    if (k == kh_end(ore->env)) {
      return ore_value_nil();
    }
    return kh_value(ore->env, k);
  }
  if (is_a(t, "factor")) {
    return ore_eval(ore, t->children[1]);
  }
  if (is_a(t, "lexp") || is_a(t, "term")) {
    v = ore_eval(ore, t->children[0]);
    for (i = 1; i < t->children_num; i += 2) {
      char* op = t->children[i]->contents;
      ore_value rhs = ore_eval(ore, t->children[i+1]);
      switch (v.t) {
        case ORE_TYPE_INT:
          {
            int iv = rhs.t == ORE_TYPE_INT ? rhs.v.i : rhs.t == ORE_TYPE_FLOAT ? (int) rhs.v.d : 0;
            if (strcmp(op, "+") == 0) { v.v.i += iv; }
            if (strcmp(op, "-") == 0) { v.v.i -= iv; }
            if (strcmp(op, "*") == 0) { v.v.i *= iv; }
            if (strcmp(op, "/") == 0) { v.v.i /= iv; }
            if (strcmp(op, "%") == 0) { v.v.i %= iv; }
          }
          break;
        case ORE_TYPE_FLOAT:
          {
            double fv = rhs.t == ORE_TYPE_INT ? (double) rhs.v.i : rhs.t == ORE_TYPE_FLOAT ? rhs.v.d : 0;
            if (strcmp(op, "+") == 0) { v.v.d += fv; }
            if (strcmp(op, "-") == 0) { v.v.d -= fv; }
            if (strcmp(op, "*") == 0) { v.v.d *= fv; }
            if (strcmp(op, "/") == 0) { v.v.d /= fv; }
            if (strcmp(op, "%") == 0) { v.v.d = ((int) v.v.d % (int) fv); }
          }
          break;
      }
    }
    return v;
  }
  if (is_a(t, "let")) {
    khint_t k = kh_put(ident, ore->env, t->children[0]->contents, &r);
    v = ore_eval(ore, t->children[2]);
    kh_value(ore->env, k) = v;
    return v;
  }
  if (is_a(t, "func")) {
    ore_value v = { ORE_TYPE_FUNC };
    v.v.f = t->children[5];
    khint_t k = kh_put(ident, ore->env, t->children[1]->contents, &r);
    kh_value(ore->env, k) = v;
    return v;
  }
  if (is_a(t, "lambda")) {
    ore_value v = { ORE_TYPE_FUNC };
    v.v.f = t->children[4];
    return v;
  }
  if (is_a(t, "call")) {
    return ore_call(ore, t);
  }
  if (t->tag[0] == '>') {
    for (i = 0; i < t->children_num; i++) {
      ore_eval(ore, t->children[i]);
    }
    return ore_value_nil();
  }
  fprintf(stderr"Unknwn operation '%s'\n", t->tag);
  return ore_value_nil();
}

ore_context*
ore_new(ore_context* parent) {
  ore_context* ore = (ore_context*) malloc(sizeof(ore_context));
  ore->env = kh_init(ident);
  ore->gc = kl_init(ident);
  ore->parent = parent;
  return ore;
}

void
ore_destroy(ore_context* ore) {
  kl_destroy(ident, ore->gc);
}

int main(int argc, char **argv) {
  if (argc != 2) {
    fprintf(stderr"usage of %s: file\n", argv[0]);
    exit(0);
  }
  mpc_parser_t* Number  = mpc_new("number");
  mpc_parser_t* Factor  = mpc_new("factor");
  mpc_parser_t* String  = mpc_new("string");
  mpc_parser_t* Ident   = mpc_new("ident");
  mpc_parser_t* Term    = mpc_new("term");
  mpc_parser_t* Lexp    = mpc_new("lexp");
  mpc_parser_t* Let     = mpc_new("let");
  mpc_parser_t* Func    = mpc_new("func");
  mpc_parser_t* Lambda  = mpc_new("lambda");
  mpc_parser_t* Call    = mpc_new("call");
  mpc_parser_t* Comment = mpc_new("comment");
  mpc_parser_t* Eof     = mpc_new("eof");
  mpc_parser_t* Stmt    = mpc_new("stmt");
  mpc_parser_t* Program = mpc_new("program");

  mpc_err_t* err = mpca_lang(MPC_LANG_DEFAULT, STRUCTURE,
      Number, Factor, String, Ident,
      Term, Lexp, Let, Lambda, Func, Call, Comment, Eof,
      Stmt, Program);
  if (err != NULL) {
    mpc_err_print(err);
    mpc_err_delete(err);
    goto leave;
  }

  mpc_result_t result;
  if (!mpc_parse_contents(argv[1], Program, &result)) {
    mpc_err_print(result.error);
    mpc_err_delete(result.error);
    goto leave;
  }

  mpc_ast_print(result.output);

  ore_context* ore = ore_new(NULL);
  ore_define_cfunc(ore, "println", ore_println);
  ore_eval(ore, result.output);
  ore_destroy(ore);

  mpc_ast_delete(result.output);

leave:
  mpc_cleanup(11,
      Number, Factor, String, Ident,
      Term, Lexp, Let, Lambda, Func, Call, Comment, Eof,
      Stmt, Program);
  return 0;
}
今回の修正で以下の俺スクリプトが動作する様になりました。
#!ore
# 計算
a = 1.0;
a = a * 2 + 1;
b = "hello";
println(b, a + 1);

# 関数宣言
func foo() {
  println(1);
}

# 関数呼出
foo();

# λ関数
c = func(){
  println("lambda");
};

# λ関数呼出
c();
次回は
  • 関数呼出への引数宣言
  • エラー処理
を行う予定です。

プログラミング言語の作り方(4)

$
0
0
Big Sky :: プログラミング言語の作り方
Big Sky :: プログラミング言語の作り方(2)
Big Sky :: プログラミング言語の作り方(3)
本日は関数スコープの実装と関数引数のバインディングを行います。
まず関数スコープを入れるという事は、メモリの破棄が必要になります。

しかし関数が呼び出された後、関数スコープ内のメモリを全て削除してしまうと戻り値に文字列を渡せなくなります。
func foo() {
  return "foo"; // この後スコープが削除される
}

a = foo(); // 壊れたメモリを参照
そこで GC を導入する必要があります。GC の実装には複数あます。詳しくは Wikipediaを参照下さい。
今回はその中の「参照カウント方式」を取ります。
まず ore_value に参照カウンタを付けます。
typedef struct _ore_value {
  ore_type t; // 変数種別
  union {
    int i;    // int の値
    double d; // double の値
    char* s;  // 文字列
    struct {
      void* env;    // 関数宣言時のスコープ
      int num_in;   // 引数の数(現状CFUNCのみ)
      union {
        void* c;    // CFUNCの関数ポインタ
        ore_func o; // ユーザ定義関数のステートメント
      } x;
    } f;     // 関数
  } v;
  int ref; // 参照カウンタ
} ore_value;
そして便利関数として ore_value_ref と ore_value_unref を実装します。
void
ore_value_real_free(ore_value* v) {
  switch (v->t) {
    case ORE_TYPE_STR:
      //printf("free %d, %p, %s\n", v->ref, v->v.s, v->v.s);
      free(v->v.s);
      v->v.s = NULL;
      break;
    case ORE_TYPE_FUNC:
      break;
  }
  v->t = ORE_TYPE_NIL;
}


void
ore_value_free(void *p) {
  ore_value* v = (ore_value*) p;
  ore_value_unref(v);
}

void
ore_value_ref(ore_value *v) {
  v->ref++;
}
参照カウント方式は一般的にコードが複雑になると思われがちですが、用法用量を守って正しく使うとそんなに難しくはありません。
複雑にならない様に env に登録/更新する関数を作ります。
ore_value
ore_get(ore_context* ore, const char* name) {
  if (!ore)
    return ore_value_nil();
  khint_t k;
  while (ore) {
    k = kh_get(ident, ore->env, name);
    if (k != kh_end(ore->env)) {
      return kh_value(ore->env, k);
    }
    ore = ore->parent;
  }
  return ore_value_nil();
}

void
ore_set(ore_context* ore, const char* name, ore_value v) {
  khint_t k;
  int r;
  while (ore) {
    k = kh_get(ident, ore->env, name);
    if (k != kh_end(ore->env)) {
      ore_value old = kh_value(ore->env, k);
      ore_value_unref(&old);
      kh_value(ore->env, k) = v; // update ref
      k = kh_put(ident, ore->env, name, &r);
      ore_value_ref(&v);
      kh_value(ore->env, k) = v;
      return;
    }
    if (ore->parent == NULL) {
      k = kh_put(ident, ore->env, name, &r);
      ore_value_ref(&v);
      kh_value(ore->env, k) = v;
      return;
    }
    ore = ore->parent;
  }
}

ore_value
ore_define(ore_context* ore, const char* name, ore_value v) {
  khint_t k = kh_get(ident, ore->env, name);
  int r;
  if (k != kh_end(ore->env)) {
    ore_value old = kh_value(ore->env, k);
    ore_value_unref(&old);
  }
  k = kh_put(ident, ore->env, name, &r);
  ore_value_ref(&v);
  kh_value(ore->env, k) = v;
}
前回説明した様に関数スコープが作られた後、識別から値を参照するにはグローバル方向に向かって検索する必要があるのでその実装を行いました。見つからなかった場合は nil を返していますが、言語によっては「Undefined Exception」の様なエラーを発生させる事もあります。
これを使って、変数(関数)宣言する時は ore_define、値を設定する時は ore_set、値を取得する時は ore_get を使えば良いです。
関数呼び出し時には、ore_new により新しい環境を作り、そこに引数名と受け渡された値のバインディングを行います。
{
  int num_in = t->children_num / 2 - 1, n = 0, i;
  if (fn.v.f.num_in != -1 && num_in != fn.v.f.num_in) {
    fprintf(stderr"Number of arguments mismatch: %d for %d\n",
      num_in, fn.v.f.num_in);
    ore->err = ORE_ERROR_EXCEPTION;
    return ore_value_nil();
  }
  ore_value* args = (ore_value*) malloc(sizeof(ore_value) * num_in);
  for (i = 2; i < t->children_num - 1; i += 2) {
    args[n++] = ore_eval(ore, t->children[i]);
  }
  ore_context* env = ore_new((ore_context*) fn.v.f.env);
  mpc_ast_t* stmts = NULL;
  mpc_ast_t* f = fn.v.f.x.o;
  n = 0;
  for (i = 2; i < f->children_num; i++) {
    if (is_a(f->children[i], "ident")) {
      if (n < num_in)
        ore_define(env, f->children[i]->contents, args[n++]);
    } else if (stmts == NULL && !is_a(f->children[i], "char")) {
      stmts = f->children[i];
    }
  }
  if (stmts) {
    v = ore_eval(env, stmts);
    if (ore->err != ORE_ERROR_EXCEPTION)
      ore->err = ORE_ERROR_NONE;
    *kl_pushp(ident, ore->unnamed) = v;
  }
  free(args);
  ore_destroy(env);
}
そして気を付けなければならないのが、実行時スコープです。上のコードでは、関数を呼び出したスコープで ore_new を呼び出すのではなく
if (is_a(t, "func")) {
  ore_value v = { ORE_TYPE_FUNC };
  v.v.f.env = ore;
  v.v.f.num_in = -1;
  v.v.f.x.o = t;
  ore_set(ore, t->children[1]->contents, v);
  return v;
}
if (is_a(t, "lambda")) {
  ore_value v = { ORE_TYPE_FUNC };
  v.v.f.env = ore;
  v.v.f.num_in = -1;
  v.v.f.x.o = t;
  return v;
}
関数が宣言された時点での環境を保持しておき、その環境から新しい環境を作る必要があります。こうしないと
#!ore
func counter() {
  var a = 0;
  return func() {
    a = a + 1;
    return a;
  };
}
count = counter();

println(count());
println(count());
この様なコードが動作しなくなります。今回の実装も以下のリポジトリから参照出来ます。
mattn/orelang - GitHub

README.md orelang 俺言語 プログラミング言語の作り方 プログラミング言語の作り方(2) プログラミング言語の作り方(3) Usage # calculation a = 1; a =...

https://github.com/mattn/orelang
そろそろエラー処理を書きたいと思います。

golang で複数のエラーをハンドリングする方法

$
0
0
FAQ に書いてあります。
Why does Go not have exceptions? - Frequently Asked Questions (FAQ) - The Go Programming Language

We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional.

Go takes a different approach. For plain error handling, Go's multi-value returns make it easy to report an error without overloading the return value. A canonical error type, coupled with Go's other features, makes error handling pleasant but quite different from that in other languages.

Go also has a couple of built-in functions to signal and recover from truly exceptional conditions. The recovery mechanism is executed only as part of a function's state being torn down after an error, which is sufficient to handle catastrophe but requires no extra control structures and, when used well, can result in clean error-handling code.

http://golang.org/doc/faq#exceptions
try-catch-finally イディオムはコードを複雑にするし、たかがファイルのオープンに失敗しただけのエラーに過剰なラベル付を強要する。 golang では複数の値を返す事が出来るので、一般的には戻り値の最後にエラーを付けて返す。

と書いてあります。
何度読んでも「例外が使いこなせません」とは読めません。すいません。複雑になるのは良くないという話はそもそもレイヤの違う話だと思いますよ。まぁ FUD なツィートで人気のある方なので、ディスカッションするつもりもないですが。
バグが発生する多くの問題は、エラーハンドリングが正しくされていない事が原因だったりします。golang の場合は戻り値が複数あった場合、戻り値を得るには必ず全ての戻り値を変数にバインドしないといけない仕様になっています。つまりは戻り値が欲しければ、error も取れという事になりますね。
package main

import (
    "fmt"
)

func doSomething(input int) (result string, err error) {
    switch {
    case input < 0:
        return "", fmt.Errorf("%d is negative value", input)
    case input < 50:
        return "less than 50"nil
    default:
        return "greater than 50"nil
    }
}

func main() {
    if r, err := doSomething(20); err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(r)
    }
}
golang では if の else ブロック内でも if 句内で宣言した変数が参照出来ます。Java の try-catch-finally だと try 内で宣言した変数は catch ブロックでは参照出来ません。それを回避する為に try よりも上部に持っていって... などというのは良くある話ですし、ネストだらけの try-catch-finally も良く見ますね。
golang でも細かいエラーハンドリングは出来ます。この辺はドキュメントを読むと書いてあります。例えば os.Openならば
func Open - The Go Programming Language

Open opens the named file for reading. If successful, methods on the returned file can be used for reading; the associated file descriptor has mode O_RDONLY. If there is an error, it will be of type *PathError.

http://golang.org/pkg/os/#Open
エラーがあった場合には PathError が返ります。
package main

import (
    "log"
    "os"
    "syscall"
)

func main() {
    f, err := os.Open("not-found-file.txt")
    if err != nil {
        pathErr := err.(*os.PathError)
        errno := pathErr.Err.(syscall.Errno)
        if errno == syscall.ENOENT {
            log.Fatalf("ファイルが見つからなかったんだと思います: %v", pathErr)
        }
        log.Fatalf("良く分からないエラーが発生しました: %v", pathErr)
    }
    f.Close()
}
ラベル付けしないからこそ、エラーは詳細に取れる様に設計されています。別のドキュメントにエラーハンドリングについて詳細に書かれています。
Error handling and Go - The Go Blog

If you have written any Go code you have probably encountered the built-in error type...

http://blog.golang.org/error-handling-and-go

Gist.vim を GitHub Enterprise Edition で使っておれられる方への注意喚起

$
0
0
これまでは、github_api_urlという設定で GitHub Enterprise Edition の API で Gist 出来る様にしていましたが、設定名が変更になり gist_api_urlとなりました。
Fixes trailing slash problem by tpoisot - Pull Request #154 · mattn/gist-vim · GitHub

Showing 3 changed files with 31 additions and 31 deletions . Show diff stats Hide diff stats 2 ð...

https://github.com/mattn/gist-vim/pull/154
気付かず :Gistしてしまうと GitHub 側にポストされてしまいますのでご注意下さい。

プログラミング言語の作り方(5)

$
0
0
Big Sky :: プログラミング言語の作り方
Big Sky :: プログラミング言語の作り方(2)
Big Sky :: プログラミング言語の作り方(3)
Big Sky :: プログラミング言語の作り方(4)

本来ならばここらあたりでエラー処理とか例外を入れるべきでしたが、先に以下の実装を行いました。
  • 配列
  • ハッシュ
  • 配列要素へのアクセス
  • ハッシュ要素へのアクセス
  • if
  • while
  • for
  • return
  • break
  • continue
現在のシンタックスは以下の通り。
#define STRUCTURE \
"                                                                       \n" \
"number     : /-?[0-9]+(\\.[0-9]*)?(e[0-9]+)?/ ;                         \n" \
"true       : \"true\" ;                                                 \n" \
"false      : \"false\" ;                                                \n" \
"nil        : \"nil\" ;                                                  \n" \
"factor     : '('<lexp> ')'                                             \n" \
"           | <number>                                                   \n" \
"           | <string>                                                   \n" \
"           | <array>                                                    \n" \
"           | <hash>                                                     \n" \
"           | <true>                                                     \n" \
"           | <false>                                                    \n" \
"           | <nil>                                                      \n" \
"           | <call>                                                     \n" \
"           | <new>                                                      \n" \
"           | <ident> ;                                                  \n" \
"string     : /\"(\\\\.|[^\"])*\"/ ;                                     \n" \
"item       : <factor> ('['<lexp> ']')+ ;                               \n" \
"prop       : <factor> ('.'<ident>)+ ;                                  \n" \
"cmp        : <factor>                                                     " \
"         (\"!=\" | \"==\" | \"<=\" | \"<\" | \">=\" | \">\" )             " \
"         <factor> ;                                                     \n" \
"call       : <ident> '('<lexp>? (','<lexp>)* ')' ;                    \n" \
"anoncall   : <factor> '('<lexp>? (','<lexp>)* ')' ;                   \n" \
"methodcall : <prop> '('<lexp>? (','<lexp>)* ')' ;                     \n" \
"array      : '['<lexp>? (','<lexp>)* ']' ;                            \n" \
"pair       : <string> ':'<lexp> ;                                      \n" \
"hash       : '{'<pair>? (','<pair>)* '}' ;                            \n" \
"ident      : /[a-zA-Z_][a-zA-Z0-9_]*/ ;                                  \n" \
"                                                                        \n" \
"term       : (<lambda> | <item> | <methodcall> | <cmp> | <prop>           " \
"         | <anoncall> | <call>                                          \n" \
"         | <factor> (('*' | '/' | '%') <factor>)*) ;                    \n" \
"lexp       : <term> (('+' | '-') <term>)* ;                             \n" \
"let_v      : <ident> '='<lexp> ';' ;                                   \n" \
"let_a      : <item> '='<lexp> ';' ;                                    \n" \
"let_p      : <prop> '='<lexp> ';' ;                                    \n" \
"else_if    : \"else\" \"if\" '('<lexp> ')''{'<stmts> '}' ;           \n" \
"else       : \"else\" '{'<stmts> '}' ;                                 \n" \
"if_stmt    : \"if\" '('<lexp> ')''{'<stmts> '}' ;                    \n" \
"if         : <if_stmt> <else_if>* <else>? ;                             \n" \
"while      : \"while\" '('<lexp> ')''{'<stmts> '}' ;                 \n" \
"for_in     : \"for\" '('<ident> \"in\" <lexp> ')''{'<stmts> '}' ;    \n" \
"var        : \"var\" <ident> '='<lexp> ';' ;                           \n" \
"vararg     : \"...\" ;                                                  \n" \
"stmts      : <stmt>* ;                                                  \n" \
"                                                                        \n" \
"lambda     : \"func\"                                                     " \
"         '('<ident>? (<vararg> | (','<ident>)*) ')''{'<stmts> '}' ; \n" \
"func       : \"func\" <ident>                                             " \
"         '('<ident>? (<vararg> | (','<ident>)*) ')''{'<stmts> '}' ; \n" \
"template   : (<var> | <func>)* ;                                        \n" \
"class      : \"class\" <ident> '{'<template> '}' ;                     \n" \
"new        : \"new\" <ident> '('<lexp>? (','<lexp>)* ')' ;            \n" \
"                                                                        \n" \
"break      : \"break\" ';' ;                                            \n" \
"continue   : \"continue\" ';' ;                                         \n" \
"return     : \"return\" <lexp> ';' ;                                    \n" \
"comment    : /#[^\n]*/ ;                                                \n" \
"eof        : /$/ ;                                                      \n" \
"stmt       : (<let_v> | <let_a> | <let_p> | <var> | <if>                  " \
"         | <while> | <for_in>                                             " \
"         | <func> | <class> | <return> | <break>                        \n" \
"         | <continue> | <comment> | (<lexp> ';')) ;                     \n" \
"program    : <stmts> <eof> ;                                            \n"
配列 arrayは簡単ですね。lexpがカンマ区切りになっているだけです。
"array      : '['<lexp>? (','<lexp>)* ']' ;                            \n" \
ハッシュは少し難しくなります。まずキーと値を組にした pairという物を宣言し、それの繰り返しという定義となります。
"pair       : <string> ':'<lexp> ;                                      \n" \
"hash       : '{'<pair>? (','<pair>)* '}' ;                            \n" \
配列、ハッシュの要素へのアクセスですが、実装方法にもよりますが代入左項を lexp として処理してしまい
a[1] = 2;
値の参照のみを行ってしまうと、実際に要素を変更する aへの変更が出来ません。値の出所をリファレンスで持っておくのも良いですが、面倒なので要素代入というステートメントで処理しています。

if、else if、else はそれぞれ AST を処理しやすい様に以下の構造を作りました。
#!ore
if (0) {
  println("foo");
else if (false) {
  println("boo");
else {
  println("zoo");
}
ref array 1 008CB850

  stmts|> 
    stmt|comment|regex:1:1 '#!ore'
    if|> 
      if_stmt|> 
        string:2:1 'if'
        char:2:4 '('
        lexp|term|factor|number|regex:2:5 '0'
        char:2:6 ')'
        char:2:8 '{'
        stmt|> 
          call|> 
            ident|regex:3:3 'println'
            char:3:10 '('
            lexp|term|factor|string|regex:3:11 '"foo"'
            char:3:16 ')'
          char:3:17 ';'
        char:4:1 '}'
      else_if|> 
        string:4:3 'else'
        string:4:8 'if'
        char:4:11 '('
        lexp|term|factor|false|string:4:12 'false'
        char:4:17 ')'
        char:4:19 '{'
        stmt|> 
          call|> 
            ident|regex:5:3 'println'
            char:5:10 '('
            lexp|term|factor|string|regex:5:11 '"boo"'
            char:5:16 ')'
          char:5:17 ';'
        char:6:1 '}'
      else|> 
        string:6:3 'else'
        char:6:8 '{'
        stmt|> 
          call|> 
            ident|regex:7:3 'println'
            char:7:10 '('
            lexp|term|factor|string|regex:7:11 '"zoo"'
            char:7:16 ')'
          char:7:17 ';'
        char:8:1 '}'
  eof|regex 
AST をそのまま保持しておき、条件を実行した結果でどのステートメント群を実行するかを処理します。
if (is_a(t, "if")) {
  ore_value v;
  int i;
  for (i = 0; i < t->children_num; i++) {
    int r = 0;
    mpc_ast_t* f = t->children[i];
    if (is_a(f, "if_stmt")) {
      r = ore_is_true(ore_eval(ore, f->children[2]));
    } else if (is_a(f, "else_if")) {
      r = ore_is_true(ore_eval(ore, f->children[3]));
    } else {
      r = 1;
    }
    if (r)
      return ore_eval(ore, ore_find_statements(f));
  }
  return ore_value_nil();
}
for や while も同様です。要約フィボナッチ数の計算が出来る様になりました。
func fib(n) {
  if (n < 2) {
    return n;
  }
  return fib(n-2) + fib(n-1);
}

println(fib(20));
実はリポジトリ内では既にクラスオブジェクトの生成が出来る様になっています。興味のある方はこちらからどうぞ。
mattn/orelang - GitHub

俺言語

https://github.com/mattn/orelang
さて、今日は本当ならばエラー処理を書きたかったのですが実は使っているパーサの mpc が AST から行番号を取れないという問題を見つけ、問題報告していた為に実装出来ませんでした。

Hope to get code location from mpc_ast_t ・ Issue #4 ・ orangeduck/mpc - GitHub

Hey. This should be added in the newest version. You can use the state member of mpc_ast_t . You can...

https://github.com/orangeduck/mpc/issues/4
報告したら実装してくれましたので、今度はエラー処理を書こうと思います。

実は、言語処理系を実装する上で少し面倒なのが return なのです。return は大域脱出になりえます。
例えば関数の中に if 文があり、その中に for 文があり、その中で return 文があると、その if 文や for 文をキャンセルして戻り値と共に大域脱出する必要があります。この実装に longjmp/setjmp を使う言語処理系もありますが、今回は return をエラーと見立ててあらゆる箇所で中断処理を実行させ、関数処理内に戻ってきたらエラー扱いではなく正しい return として処理させるという方法を使っています。
なので例えば関数内でなければ不正なエラーとなる訳です。逆に都合がいいですね。break や continue も同じ手法を使っています。

ping pong でタスク管理

$
0
0
よし、卓球をしよう。
pingpong
ちょっとしたクラサバで何かを動かす時、クライアントが死んだらサーバも死んで欲しい時がある。自分で作ったプログラムならいいけれど、そうじゃないならいちいちサーバで CTRL-C タイプしなきゃならなかったりする。

「そこ自動でしょ!」

と思ったのでツールを作ってみました。
mattn/pingpong - GitHub
https://github.com/mattn/pingpong
2つのツールで構成されます。pping は引数で与えられたタスク名で ppong に一定周期で生存通知を行います。ppong は指定されたタスクが起動していなければ起動し、一定期間 ping が来なければそのプロセスを終了します。
pping は引数を取る事ができ、例えばサーバのあるタスクを起動させてからクライアントを起動したい場合には以下の様に実行します。

$ pping -n game-server game-client
こうすると ppong は game-server.jsonを読み
    "name""game-server",
    "args": ["-f""game.conf"],
    "timeout"30
予め指定されたコマンドと引数でサーバプロセスを起動します。ppong は30秒間 ping が来なければ強制的に pping を終了します。複数クライアントいる場合、1台でも起動していればサーバは起動し続けます。

本当は ffmpeg/ffserver でストリーミングをやっていて、ffmpeg が終了したら ffserver を落としたいという理由で作りましたが、色んな用途に使えるかもしれないので汎用ツールにしてみました。
何に使えるかは分かりませんが、よろしければどうぞ。

Golang のオフィシャルが提供するインタフェースまとめ

$
0
0
golang が提供するインタフェースの中で代表的な物の使い方をまとめてみる。

io.Reader

type Reader interface {
    Read(p []byte) (n int, err error)
}
ご存じ io.Reader。このシグネチャの Read を実装しておけば golang のありとあらゆる入力機能に対して自分のコードを提供する事が出来る。 例えば永遠に「おっぱい」と言い続ける Reader だと以下の様な実装になる。
package main

import (
    "io"
    "os"
)

var text = []rune("おっぱい")

type OppaiReader struct {
    n int
}

func (r *OppaiReader) Read(p []byte) (interror) {
    in := len(p)
    nw := 0
    for i := 0; i < in; i++ {
        cb := []byte(string(text[r.n%len(text)]))
        if nw+len(cb) > in {
            break
        }
        cbl := len(cb)
        copy(p[nw:nw+cbl], cb)
        nw += cbl
        r.n++
    }
    return nw, nil
}

func main() {
    io.Copy(os.Stdout, &OppaiReader{})
}
ただしこういうことをする場合は、RuneReader を使った方が綺麗かもしれない。

io.Writer

type Writer interface {
    Write(p []byte) (n int, err error)
}
io.Readerのペアとなるインタフェース。同じくこの関数を実装しておけば golang のあらゆる出力機能に対して自分のコードを提供出来る。
例えば出力される文字列を Pascal Case で出力する Writer だと以下のコードになる。
package main

import (
    "fmt"
    "io"
    "os"
)

type pascalCaseWriter struct {
    w    io.Writer
    last byte
}

func (w *pascalCaseWriter) Write(p []byte) (interror) {
    r := 0
    for n, _ := range p {
        switch w.last {
        case '''\t''\r''\n'0:
            if 'a' <= p[n] && p[n] <= 'z' {
                p[n] -= 32
            }
        }

        nw, err := w.w.Write(p[n : n+1])
        if err != nil {
            return r + nw, err
        }
        w.last = p[n]
    }
    return r, nil
}

func NewPascalCaseWriter(w io.Writer) *pascalCaseWriter {
    return &pascalCaseWriter{w, 0}
}

func main() {
    w := NewPascalCaseWriter(os.Stdout)
    fmt.Fprintln(w, "hello world")
}

io.Closer

type Closer interface {
    Close() error
}
Closeメソッドを提供するインタフェースとなるが、実際には単品で使われる事はなく、Reader かつ Closer、Writer かつ Closer という使われ方になる。

io.Seeker

type Seeker interface {
    Seek(offset int64, whence int) (int64error)
}
名前の通り、指定オフセット位置でシーク出来るインタフェースとなる。これを実装した代表的な物としては os.Fileがある。

io.ReadWriter

type ReadWriter interface {
    Reader
    Writer
}
Reader でありかつ Writer であるインタフェースで双方向通信を行う為のインタフェース。代表的な実装としては net.Connがある。

io.WriteCloser

type WriteCloser interface {
    Writer
    Closer
}
こちらは os.Createで作成した os.Fileが実装するインタフェース。

io.ByteReader

type ByteReader interface {
    ReadByte() (c byte, err error)
}
ある入力から1バイトずつ読み込む事が出来るインタフェース。tail 的な処理を書きたい場合に用いる。

その他、組み合わせで用いる事が出来る io.ReadWriteCloserio.ReadSeekerio.WriteSeekerio.ReadWriteSeekerio.ByteScannerio.ByteWriterio.RuneReaderio.RuneScannerがある。

compress/flate/inflate.Reader

type Reader interface {
    io.Reader
    io.ByteReader
}
compress 圧縮の Reader を提供する。圧縮されたファイル等をこれを用いて Read すると等価的に内容が読めるという物。
実際には NewReaderを用いて使用する。

database/sql.Driver

type Driver interface {
    Open(name string) (Conn, error)
}
データベースドライバ実装を表すインタフェース。新しいデータベースのバインディングをサポートする場合、まずこのインタフェース実装を書くことになる。

database/sql.Execer

type Execer interface {
    Exec(query string, args []Value) (Result, error)
}
通常は database/sqlでは Prepareして Execするのが普通の使い方だが、データベース接続が Execer を実装している場合は、いちいち Prepare等せずいきなり接続から実行できる事を明示する事が出来る。
ドライバの実装は必須ではない。

database/sql.Queryer

type Queryer interface {
    Query(query string, args []Value) (Rows, error)
}
Execer と同様に、結果を得るクエリの場合 Prepareして Queryする必要があるが、データベース接続から直接 Query 出来るドライバである事を明示する事が出来る。
ドライバの実装は必須ではない。

database/sql.ColumnConverter

type ColumnConverter interface {
    ColumnConverter(idx int) ValueConverter
}
golang の database/sqlは自動で型変換を行ってはくれるが、ある特殊な型に対して変換を行いたい場合にはこのインタフェースを実装しする。このインタフェースを実装する場合、ValueConverter も実装する必要がある。

database/sql.ValueConverter

type ValueConverter interface {
    ConvertValue(v interface{}) (Value, error)
}
各型に対する変換処理を提供するインタフェース。例えば bool 型から各型への変換を提供する boolType では以下の実装になっている。
func (boolType) ConvertValue(src interface{}) (Value, error) {
    switch s := src.(type) {
    case bool:
        return s, nil
    case string:
        b, err := strconv.ParseBool(s)
        if err != nil {
            return nil, fmt.Errorf("sql/driver: couldn't convert %q into type bool", s)
        }
        return b, nil
    case []byte:
        b, err := strconv.ParseBool(string(s))
        if err != nil {
            return nil, fmt.Errorf("sql/driver: couldn't convert %q into type bool", s)
        }
        return b, nil
    }

    sv := reflect.ValueOf(src)
    switch sv.Kind() {
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        iv := sv.Int()
        if iv == 1 || iv == 0 {
            return iv == 1nil
        }
        return nil, fmt.Errorf("sql/driver: couldn't convert %d into type bool", iv)
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        uv := sv.Uint()
        if uv == 1 || uv == 0 {
            return uv == 1nil
        }
        return nil, fmt.Errorf("sql/driver: couldn't convert %d into type bool", uv)
    }

    return nil, fmt.Errorf("sql/driver: couldn't convert %v (%T) into type bool", src, src)
}
ドライバの実装は必須ではない。

database/sql.Valuer

type Valuer interface {
    Value() (Value, error)
}
golang のデータベースドライバを実装するパッケージに対して golang 側から値を要求する際に用いられるインタフェース。
ドライバの実装は必須ではない。

database/sql.Scanner

type Scanner interface {
    Scan(src interface{}) error
}
rows.Scanに対して自前の実装を入れたい場合にインプリメントする。
ドライバの実装は必須ではない。

binary.ByteOrder

type ByteOrder interface {
    Uint16([]byteuint16
    Uint32([]byteuint32
    Uint64([]byteuint64
    PutUint16([]byteuint16)
    PutUint32([]byteuint32)
    PutUint64([]byteuint64)
    String() string
}
binary.BigEndianbinary.LittleEndianを実装に持つインタフェース。ユーザがさらに実装を追加する事はおそらく無い、BigEndian と LittleEndian どちらでやってくるか分からない処理に対して等価的に処理を行いたい場合には、このインタフェースを引数に取って扱う。

encoding.BinaryMarshaler

type BinaryMarshaler interface {
    MarshalBinary() (data []byte, err error)
}
あるインスタンスがバイナリ列へシリアライズ可能かどうかを示すインタフェース。

binary.BinaryUnmarshaler

type BinaryUnmarshaler interface {
    UnmarshalBinary(data []byteerror
}
あるインスタンスがバイナリ列からデシリアライズ可能かどうかを示すインタフェース。

encoding.TextMarshalerencoding.TextUnmarshalerも同様に、文字列に対する交換インタフェースを提供する。

encoding/gob.GobEncoder

type GobEncoder interface {
    GobEncode() ([]byteerror)
}
golang には標準で Gob というシリアライザブルフォーマットに対するインタフェースが提供されているが、インスタンスに対して Gob フォーマットへの交換が可能かどうかを明示出来る。

encoding/gob.GobDecoder

type GobDecoder interface {
    GobDecode([]byteerror
}
同様に Gob からある型への交換が可能かどうかを明示出来る。

encoding/json.Unmarshaler

type Unmarshaler interface {
    UnmarshalJSON([]byteerror
}
JSON を示すバイト列からある型への交換が可能である事を示す。

encoding/json.Marshaler

type Marshaler interface {
    MarshalJSON() ([]byteerror)
}
Unmarshaler とは逆にバイト列からある型への交換が可能である事を示す。
Marshaler と Unmarshaler の例を以下に示す。
package main

import (
    "encoding/base64"
    "encoding/json"
    "fmt"
    "log"
)

type User struct {
    Name     string
    Password string
}

func (user *User) UnmarshalJSON(p []byteerror {
    var v map[string]interface{}
    err := json.Unmarshal(p, &v)
    if err != nil {
        return err
    }
    for _, f := range []string{"Name""name"} {
        if vv, ok := v[f]; ok {
            user.Name = fmt.Sprint(vv)
            break
        }
    }
    for _, f := range []string{"Password""password""PassWord""passWord"} {
        if vv, ok := v[f]; ok {
            b, err := base64.StdEncoding.DecodeString(fmt.Sprint(vv))
            if err != nil {
                return nil
            }
            user.Password = string(b)
            break
        }
    }
    return nil
}

func (user *User) MarshalJSON() ([]byteerror) {
    return []byte(fmt.Sprintf(`{"Name": %q: "Password": %q}`,
        user.Name, base64.StdEncoding.EncodeToString([]byte(user.Password)))), nil
}

func main() {
    p := &User{"ウルトラマン""henshin"}
    b, err := p.MarshalJSON()
    if err != nil {
        log.Fatal(err)
    }
    // {"Name": "ウルトラマン", "Password": "aGVuc2hpbg=="}
    fmt.Println(string(b))

    err = p.UnmarshalJSON([]byte(`
    {
        "Name": "仮面ライダー",
        "Password": "aGVuc2hpbg=="
    }
    `))
    if err != nil {
        log.Fatal(err)
    }
    // 仮面ライダー henshin
    fmt.Printf("%s : %s\n", p.Name, p.Password)
}
encoding.xml.Unmarshalerencoding.xml.Marshalerも json と同様。

flag.Getter

type Getter interface {
    Value
    Get() interface{}
}
golang の flag パッケージを用いて、プログラム引数から独自の型へ変換を行いたい場合に使う。

fmt.Formatter

type State interface {
    // Write is the function to call to emit formatted output to be printed.
    Write(b []byte) (ret int, err error)
    // Width returns the value of the width option and whether it has been set.
    Width() (wid int, ok bool)
    // Precision returns the value of the precision option and whether it has been set.
    Precision() (prec int, ok bool)

    // Flag reports whether the flag c, a character, has been set.
    Flag(c intbool
}
fmt.Printfと同様の処理を自前実装したい場合に使う。

fmt.Stringer

type Stringer interface {
    String() string
}
実はこのインタフェースが一番使われる事が多い。デバッグではよく fmt.Printlnを使う事が多いと思うが、これを実装しておくと独自の型が fmt.Println等に渡った時にどの様に表示されるかを実装する事が出来る。
package main

import (
    "fmt"
)

type Oppai bool

func (o Oppai) String() string {
    if o {
        return "(・)(・)"
    }
    return "(◎)(◎)"
}

func main() {
    var o Oppai

    o = true
    fmt.Println(o) // (・)(・)

    o = false
    fmt.Println(o) // "(◎)(◎)"
}
fmt.GoStringerは同様のインタフェースではあるが、実際には内部処理のみで使われている。

fmt.Scanner

type Scanner interface {
    Scan(state ScanState, verb runeerror
}
fmt.Scanと同様の処理を自前実装したい場合に使う。

image/draw.Quantizer

type Quantizer interface {
    Quantize(p color.Palette, m image.Image) color.Palette
}
独自のカラーパレットを実装したい場合に使用する。gif は 256 色しか出力出来ないのでこれを使って減色処理を行っている。
Drawerはこのパレットを用いて描画を行う際に実装する。

image/jpeg.Reader

type Reader interface {
    io.Reader
    ReadByte() (c byte, err error)
}
jpeg を自前でデコードしたい場合に実装する。

net/http.RoundTripper

type RoundTripper interface {
    RoundTrip(*Request) (*Response, error)
}
http のトランスポート層を抽象化する為に用いられるインタフェース。
デフォルトのタイムアウトやダイアラ、未知のプロキシに対応する場合はこれを実装する事になる。golang の http.Clientfile://な URL でも Get する事が出来るが、これは内部でスキーマを判定して RoundTripper を切り替える事で実現している。

net/http.Handler

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
ご存じ http.Handler。これを実装しておくと、とりあえず golang 標準のウェブサーバに対するリクエストを処理出来る。
例えば go-uwsgiはこのインタフェースを実装する事で uWSGI インタフェースを実現している。

net/http.ResponseWriter

type ResponseWriter interface {
    Header() Header
    Write([]byte) (interror)
    WriteHeader(int)
}
golang 標準の Web サーバ機能を使う場合にレスポンスを書き込むインタフェース。実際にはこれをユーザ側が実装する事はまずない。主にテスト結果を得る為に用いられる。

net/http.Flusher

内部処理やテストで用いられる。

net/http.Hijacker

type Hijacker interface {
    Hijack() (net.Conn, *bufio.ReadWriter, error)
}
golang の http 通信は大部分が隠ぺいされている。しかし自前で接続を切ってしまったり、https の connect メソッドを実現する場合には都合が悪いインタフェースだ。そこで req.Hijack()を呼び出し、http 以外の通信を行う事が出来る。
websocket パッケージはこれを使って http ネゴシエーションを実現している。

net/http.CloseNotifier

type CloseNotifier interface {
    CloseNotify() <-chan bool
}
ResponseWriterを自前実装した場合、クライアントから接続を切られた事を検知する為に実装するインタフェース。

net.Listener

type Listener interface {
    Accept() (c Conn, err error)
    Close() error
    Addr() Addr
}
自前実装をソケットとして扱わせる為に実装するインタフェース。上記で紹介した go-uwsgi でも自前で Accept を実装する事で http サーバとの間の通信層で uWSGI を喋っている。

sync.Locker

type Locker interface {
        Lock()
        Unlock()
}
sync.Mutexが実装しているが、自前でもロックインタフェースを実装したい場合にインプリメントする。
実装するのは自由だが、インタフェースが取り決められていないと発散するのでそれを定義しているといった所だろうか。

以上、golang の標準パッケージに含まれる代表的なインタフェースを紹介してみた。
golang のコードがエレガントになるかどうかの半分は、このインタフェースにかかっている。ぜひ極めましょう。

Vim で markdown 内に書かれているプログラミング言語をハイライト

$
0
0
vim に同梱されている markdown シンタックスは syn includeに対応しているので指定で色が付けられる。
github 等では README.md やコメント欄に
```javascript
function hasegawa_san_hidoi() {
  alert('ひどい');
}
```
この様な記述をする事で javascript の色付けが適用されますが、vim でも可能です。
ただしデフォルトでは無効にされているので以下の様に設定を行います。
let g:markdown_fenced_languages = [
\  'coffee',
\  'css',
\  'erb=eruby',
\  'javascript',
\  'js=javascript',
\  'json=javascript',
\  'ruby',
\  'sass',
\  'xml',
\]
お好みの言語を追加して下さい。こうすると上記のテキストが以下の様にハイライトされます。
```javascript
function hasegawa_san_hidoi() {
  alert('ひどい');
}
```
Web制作者のためのSublime Textの教科書 今すぐ最高のエディタを使いこなすプロのノウハウWeb制作者のためのSublime Textの教科書 今すぐ最高のエディタを使いこなすプロのノウハウ
上野正大
インプレスジャパン / ¥ 2,592 (2014-03-20)
 
発送可能時間:在庫あり。

はじめてのMarkdown―軽量マークアップ言語の記法と使い方 (I・O BOOKS)はじめてのMarkdown―軽量マークアップ言語の記法と使い方 (I・O BOOKS)
清水 美樹
工学社 / ¥ 2,052 (2014-05)
 
発送可能時間:在庫あり。

いつでもどこでも Vim から :help する

$
0
0
お前は何を言っているんだ
いつでもどこでも Emacsから :help する - Life is very short

[vim][emacs] いつでもどこでも Emacsから :help する [coffee-mode][emacs] coffee-mode 0.5.2 released [WIP][emacs][...

http://d.hatena.ne.jp/syohex/20140527/1401188929
いつでもどこでも :help する - Vim :help

...

http://vim-help-jp.herokuapp.com/
Emacs から Vim Help を :helpで検索できるのに、Vim から出来ないなんて!出来るべきだろ!

リポジトリ
mattn/ctrlp-vimhelpjp - GitHub
https://github.com/mattn/ctrlp-vimhelpjp
CtrlP で検索出来ます。コマンド名は「:VimHelpJp」です。引数で直接ヘルプを参照出来ますし、コマンドライン補完出来ます。
vimhelpjp1
Vim Help Jp のヘルプが見れます。
vimhelpjp2

libuv と http-parser を使って高速なウェブサーバ書いてみた。

$
0
0
どうしても高速なWeb サーバが書きたくなったので joyent の libuvhttp-parser (nodejs が内部で使っているライブラリ)を使ってWeb サーバを書いてみた。
mattn/http-server - GitHub
https://github.com/mattn/http-server
I/O は全て非同期で行いブロッキングしない作りとしました。これで絶対速くなるとは言わないけど、少なくともスケールはするんじゃないかと思います。
Date ヘッダとか、65536 バイト以上の POST ペイロードとか色々省いてるのでツッコミビリディ高いですが、ひとまず GET でファイルのサーブが出来る状態にはしたのでベンチマークを取ってみました。

# nginx
$ ab -k -c 10 -n 10000 http://127.0.0.1/

# http-server
$ ab -k -c 10 -n 10000 http://127.0.0.1:7000/
Core i5/3GHz Ubuntu 14.04

5回程計測した平均で nginx が 25000req/sec、http-server が 24500req/sec でした。ほぼ互角までは来たのかなーと思います。
現状キャッシュなんかは考えてないので、純粋に非同期に拘っただけのコードになっています。

ちなみに mruby-uvmruby-httpで書いたWebサーバはLinux で 9000req/sec、Windows(Core i5/3GHz)で 5000req/sec くらいしか出ませんでしたが、全てC言語で書いたこのサーバだと Linux 24500req/sec、Windows でも 15000req/sec は出ます。

研究材料として使って行こうと思います。

golang で chan の送受信を「指」で書けるようにする魔改造

$
0
0
元ネタ
「:」を引数の名前と型の間に入れられるようしたりにする魔改造 - moriyoshiの日記

とある言語が func halfOpenRangeLength(start: Int, end: Int) -> Int { return end - start } println (halfOpe...

http://moriyoshi.hatenablog.com/entry/2014/06/03/114845
Go のソースを落としてきて
diff -r fde405c62fca src/cmd/gc/lex.c
--- a/src/cmd/gc/lex.c  Tue Jun 03 11:44:17 2014 +0900
+++ b/src/cmd/gc/lex.c  Tue Jun 03 12:49:01 2014 +0900
@@ -1308,6 +1308,11 @@
        if(c >= Runeself) {
            ungetc(c);
            rune = getr();
+           if (rune == 0x261c) {
+               c = LCOMM;
+               goto lx;
+           }
+
            // 0xb7 · is used for internal names
            if(!isalpharune(rune) && !isdigitrune(rune) && (importpkg == nil || rune != 0xb7))
                yyerror("invalid identifier character U+%04x", rune);
を当てて
$ (cd src/cmd/gc && rm y.tab.h && make)
とする。
package main

import (
    "fmt"
)

func main() {
    鼻 := make(chan string)
    go func() {
        鼻 ☜ "ハナクソ"
    }()

    fmt.Println(☜鼻)
}
$ go run test.go
ハナクソ
どーん

Windows のコマンドプロンプトを10倍便利にするコマンド「peco」

$
0
0
Windows ユーザのごく一部には、コマンドプロンプトが無いと生きられない民族がいます。そしてその民族の一部には cygwin や msys bash 等といった、サードパーティなシェル(powershell?何それ聞こえなーい)を使いたがらない種族もいます。
私もその種族なのですが、今日はそういった世界中のWindowsエンジニアの中でも数パーセントしかいないであろう人達にとって便利なTipsを紹介したいと思います。(オマケでbash連携もあるよ)
lestrrat/peco - GitHub
https://github.com/lestrrat/peco
peco
以前、私も同じようなツールとして gofというのを作りましたが、peco はこれをもっと簡素に、もっとカスタマイザブルにした物になります。
一見、UNIX でしか動作しなさそうに見えますが、gof ど同様に Windows でも動作します。マルチバイト文字列も扱えます(ただし標準入力は utf-8 にする必要があるので必要な方は iconv コマンドや nkf コマンドをパイプに加えて下さい)。
peco の動作は非常にシンプルで、標準入力から与えられた行を UI で選択し、選んだ行を標準出力に書き込みます。キーのカスタマイズも出来ますので、詳しく知りたい方は上記リンクを参照下さい。

で、Windows でこの peco をどう使うかですが、今回 pcdというコマンドを作ってみました。
@echo off

if "%1" equ "add" (
  if "%2" neq "" (
    echo %2 >> "%USERPROFILE%\.peco-cd"
    goto end
  )
  goto usage
)
if "%1" equ "edit" (
  goto edit
)
if "%1" neq "" (
  goto usage
)
goto query

:usage
echo %0 [add PATH ^| edit]
goto end

:edit
if "%EDITOR%" neq "" (
  "%EDITOR%" "%USERPROFILE%\.peco-cd"
  goto end
)
notepad "%USERPROFILE%\.peco-cd"
goto end

:query
rem NOTE:
rem
rem If you have a problem caused by character-set, modify below part like:
rem   'type ^"%USERPROFILE%\.peco-cd^" ^| iconv -f char -t utf-8 ^| peco'
rem
for /f %%i in ('type ^"%USERPROFILE%\.peco-cd^" ^| peco') do (
  cd %%i
  break
)

:end
良く使うフォルダに簡単に移動できるコマンドです。まず良く移動するフォルダを登録します。
C:\>pcd add c:\vagrant
C:\>pcd add c:\users\mattn\vimfiles
めんどくさい人は
C:\>pcd edit
とするとエディタが起動し、そこで編集出来ます。そして
C:\>pcd
とすると
pcd
peco が起動しますのでそこで移動したいフォルダを絞り込み、ENTER で確定するとそのフォルダに移動できます。
面倒な手順が省略できて便利かと思います。

またこれを応用すると golang を使った開発も便利に出来ます。
ghqを使ったローカルリポジトリの統一的・効率的な管理について - delirious thoughts
http://blog.kentarok.org/entry/2014/06/03/135300
上記の記事では UNIX シェルコマンドが紹介されていますが、これを Windows のバッチファイルで書くと以下になります。
peco-ghq.bat
@echo off

for /f %%i in ('ghq list -p ^| peco') do (
  cd %%i
  break
)
これで、「peco のソースを確認したい」と思ったら何時でも何処でも「peco-ghq」を実行するだけで
peco-ghq
peco のフォルダへ移動する事が出来ます。

番外編

基本的に perco や peco は zsh と連携して使用される事が多いですが bash でも問題なく使えます。例えば以下を $HOME/.bashrcに追加しておけば <C-x><C-g>で ghq による github リポジトリ作業フォルダへの移動が、<C-x><C-r>でコマンド履歴の選択が行える様になります。
select-ghq() {
  DIR=$(ghq list -p | peco)
  [ -n "$DIR" ] && cd $DIR
}
select-history() {
  CMD=$(history | sed 's/^\s[0-9]\+\s*//' | peco)
  [ -n "$CMD" ] && cd $CMD
}
bind '"\C-x\C-g":"select-ghq\n"'
bind '"\C-x\C-r":"select-history\n"'
なお、peco は golang で開発されており、exe ファイル1個だけあればランタイム等は必要ありません。

peco で migemo が使える様になった。

$
0
0
peco に custom-matcher という仕組みを入れて頂きました。
User CustomMatcher by mattn - Pull Request #65 - lestrrat/peco - GitHub
https://github.com/lestrrat/peco/pull/65
~/.config/peco/config.jsonに以下の様に CustomMatcherを追加します。
{
    "CustomMatcher": {
        "C/Migemo": [
            "c:/dev/peco-cmigemo/peco-cmigemo.exe",
            "$QUERY"
        ]
    }
}
例は Windows ですが、unix でも動きます。配列部分は実行するコマンド引数になり、$QUERYがクエリになります。
CustomMatcher は自作する事が出来ます。標準入力から行を読み込み、引数をパターンとして絞りこんだ結果を標準出力に吐き出せばOKです。いわゆる自前 grep ですね。
以下のリポジトリに cmigemo を使った CustomMatcher を置きました。
mattn/peco-cmigemo - GitHub
https://github.com/mattn/peco-cmigemo
これをビルドした実行モジュール peco-cmigemoと cmigemo が使う辞書(UTF-8である必要があります)は同じ位置に dict/migemo-dictという形で置く必要があります。
peco-cmigemo
欲しかった機能の殆どが peco に入ってきたし、かなり便利になった。

peco の使い方 - Windows 編

$
0
0
前回の記事で「peco の使い方が分からない」という意見を幾らか頂いたのでサンプルを。
例えば、Qiitaの「Go」タグのRSSを取ってきて、タイトルを一覧して選んだらブラウザが起動するスクリプトを書きたいとする。
@echo off

for /f %%i in ('more +8 %~f0 ^| perl -S - ^| peco --null') do (
  start %%i
  break
)
exit /b 0
use strict;
use warnings;

use XML::Feed;
use YAML::Syck;

my $feed = XML::Feed->parse(URI->new('http://qiita.com/tags/go/feed'))
  or die XML::Feed->errstr;
for my $entry ($feed->entries) {
  print $entry->title . "\0" . $entry->link . "\n";
}
これを peco-feed.batというファイルに保存して実行すると
peco-feed
タイトルが表示され、選ぶとブラウザが起動して記事を表示してくれます。便利ですね。
※ 実行には perl が必要です。

GitHub で一番 star を貰ったリポジトリを調べる方法

$
0
0
GitHub で一番お星さまを貰ったリポジトリを調べてみた。
stars:>1 user:mattn
で GitHub 検索する。(参考URL)
ユーザ名の所はご自分のIDをお使い下さい。ちなみに僕のリポジトリのトップ10は...
リポジトリStar数Fork数
mattn/emmet-vim1854149
mattn/gist-vim88498
mattn/go-gtk48480
mattn/gom43318
mattn/go-sqlite3414108
mattn/webapi-vim20323
mattn/go-xmpp15457
mattn/growl-for-linux15316
mattn/go-webkit13718
mattn/go-v813319
こんな感じでした。vim 物が多いですね。star ありがとうございました。

IIS で golang やろう

$
0
0
golang で Web と言えば、net/http でハンドラ書いて http.ListenAndServe を呼び出すサーバ方式が思い浮かびますが、他にも選択はあるはずです。
mattn/go-cgi - GitHub
https://github.com/mattn/go-cgi
golang で CGI が書けます。

まず上記リポジトリを clone して go-cgi.exe を作ります。
git clone https://github.com/mattn/go-cgi
cd go-cgi
go build
次に「管理ツール」から「インターネットインフォメーションサービス」を起動し、サイトに仮想ディレクトリを足します。
IIS
「ハンドラーマッピング」を選び、一覧上を右クリックして「スクリプトマップの追加」を選択します。
スクリプトマップ
「要求パス」は *.go、「実行可能ファイル」に先程ビルドした go-cgi.exe へのパスを、「名前」に CGI-goを入力します。
要求の制限
「要求の制限」をクリックして、「要求のマップ先が次の場合のみハンドラーを呼び出す」にチェックを入れ、「ファイル」を選び、あとは「OK」をクリックします。

最後に画面左のツリーの最上部にあるPC名をクリックして「ISAPIおよびCGIの制限」を選び、一覧に表示される CGI-go を右クリックして「許可」に変更します。
ISAPIおよびCGIの制限

これで IIS 上で拡張子 goのファイルが CGI として実行出来る準備が整いました。
Apache/nginx で動作させる場合は、これから作る CGI の拡張子を cgiにしてファイルの先頭行に #!/usr/local/bin/go-cgiといった感じに shebang を書くと動きます。
なお、go コマンドへパスを通していない場合は、これから説明する CGI を置くディレクトリに .go-cgiというフォルダを作り、そこに envというファイルを作成します。そこに GOROOT=c:/goといった感じに GOROOT を教えてあげるおまじないを書くと動く様になると思います。

さて CGI を書きましょう。上記で仮想ディレクトリをマッピングしたディレクトリに移動し、例として foo.goというファイルを以下の様に作ります。
package main

import (
    "fmt"
)

func main() {
    fmt.Print("Content-Type: text/html;\r\n\r\n")
    fmt.Println("Hello World")
}
ブラウザから http://localhost/mattn/foo.goを開くと Hello Worldが表示されるかと思います。
go-cgi は /tmp/go-cgi(Windows だと %TEMP%\go-cgi)、もしくは CGI ファイルと同じディレクトリに .go-cgi というディレクトリを作り、そこにハッシュ値で管理された go ファイルを生成すると同時にコンパイルして実行しています。
なお、golang の net/http は CGI を書く場合でも http.Handler を使う事が出来ます。
#! go-cgi
package main

import (
    "fmt"
    "net/http"
    "net/http/cgi"
)

func main() {
    cgi.Serve(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type""text/plain; charset=utf-8")
        fmt.Fprintf(w, "Hello %s", r.FormValue("name"))
    }))
}
簡単ですね。Windows の皆さんもぜひ golang で Web やりましょう。
Viewing all 121 articles
Browse latest View live