2008/01/20

Factor : C ライブラリインターフェイス(FFI)で鬼車を使ってみる

Factor の FFI がよくできているらしい(The fun factor in programming)ので、練習として鬼車を Factor の FFI で使ってみました。

まずは前準備ですが ~/.factor-rc でボキャブラリのパスを追加します。
今回の鬼車ボキャブラリは ~/letter/factor/lib/oniguruma 以下に作成します。

! -*- mode: factor ; -*-
USING: io.files vocabs.loader namespaces sequences
;

! Emacs を使う
USE: editors.emacs

! ~/letter/factor/lib を vocab-roots に追加する。
home "letter/factor/lib" path+
vocab-roots get push-new


libonig.so のインターフェイスを定義していきます。
add-library でシェアードライブラリを読み込みます。
TYPEDEF: で C の型と Factor の型を対応付けます。
定数定義は通常のワード定義(: ;)を inline で。
構造体は C-STRUCT:
で、おもしろいのが LIBRARY: に続く FUNCTION: です。
ほとんど C の関数定義のまま書くことができます。
./libonig/lobonig.factor
USING: alien alien.syntax alien.c-types
;

IN: oniguruma.libonig

: load-oniguruma-library ( -- )
"oniguruma" "libonig.so" "cdetl" add-library ; parsing

load-oniguruma-library

TYPEDEF: void* regex_t*
TYPEDEF: void** regex_t**
TYPEDEF: uchar* UChar*
TYPEDEF: uchar* OnigUChar*
TYPEDEF: uint OnigOptionType
TYPEDEF: void* OnigEncoding
TYPEDEF: void* OnigSyntaxType*
TYPEDEF: void* OnigRegion*

! regex_t state
: ONIG_STATE_NORMAL 0 ; inline
: ONIG_STATE_SEARCHING 1 ; inline
: ONIG_STATE_COMPILING -1 ; inline
: ONIG_STATE_MODIFY -2 ; inline

! options
: ONIG_OPTION_NONE 0 ; inline
: ONIG_OPTION_IGNORECASE 1 ; inline
: ONIG_OPTION_EXTEND 2 ; inline
: ONIG_OPTION_MULTILINE 4 ; inline
: ONIG_OPTION_SINGLELINE 8 ; inline
: ONIG_OPTION_FIND_LONGEST 16 ; inline
: ONIG_OPTION_FIND_NOT_EMPTY 32 ; inline
: ONIG_OPTION_NEGATE_SINGLELINE 64 ; inline
: ONIG_OPTION_DONT_CAPTURE_GROUP 128 ; inline
: ONIG_OPTION_CAPTURE_GROUP 256 ; inline
! options (search time)
: ONIG_OPTION_NOTBOL 512 ; inline
: ONIG_OPTION_NOTEOL 1024 ; inline
: ONIG_OPTION_POSIX_REGION 2048 ; inline
: ONIG_OPTION_MAXBIT 4096 ; inline
! defualt option
: ONIG_OPTION_DEFAULT ONIG_OPTION_NONE ; inline

! normal return
: ONIG_NORMAL 0 ; inline
: ONIG_MISMATCH -1 ; inline
: ONIG_NO_SUPPORT_CONFIG -2 ; inline

C-STRUCT: OnigCaptureTreeNode
{ "int" "group" }
{ "int" "beg" }
{ "int" "end" }
{ "int" "allocated" }
{ "int" "num_childs" }
{ "OnigCaptureTreeNode**" "childs" } ;


C-STRUCT: OnigRegion
{ "int" "allocated" }
{ "int" "num_regs" }
{ "int*" "beg" }
{ "int*" "end" }
{ "OnigCaptureTreeNode*" "history_root" } ;

C-STRUCT: OnigErrorInfo
{ "OnigEncoding" "enc" }
{ "OnigUChar*" "par" }
{ "OnigUChar*" "par_end" } ;

: onig-sym ( name -- alien )
"oniguruma" load-library dlsym ; inline

: ONIG_ENCODING_ASCII "OnigEncodingASCII" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_1 "OnigEncodingISO_8859_1" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_2 "OnigEncodingISO_8859_2" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_3 "OnigEncodingISO_8859_3" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_4 "OnigEncodingISO_8859_4" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_5 "OnigEncodingISO_8859_5" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_6 "OnigEncodingISO_8859_6" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_7 "OnigEncodingISO_8859_7" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_8 "OnigEncodingISO_8859_8" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_9 "OnigEncodingISO_8859_9" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_10 "OnigEncodingISO_8859_10" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_11 "OnigEncodingISO_8859_11" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_13 "OnigEncodingISO_8859_13" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_14 "OnigEncodingISO_8859_14" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_15 "OnigEncodingISO_8859_15" onig-sym ; inline
: ONIG_ENCODING_ISO_8859_16 "OnigEncodingISO_8859_16" onig-sym ; inline
: ONIG_ENCODING_UTF8 "OnigEncodingUTF8" onig-sym ; inline
: ONIG_ENCODING_UTF16_BE "OnigEncodingUTF16_BE" onig-sym ; inline
: ONIG_ENCODING_UTF16_LE "OnigEncodingUTF16_LE" onig-sym ; inline
: ONIG_ENCODING_UTF32_BE "OnigEncodingUTF32_BE" onig-sym ; inline
: ONIG_ENCODING_UTF32_LE "OnigEncodingUTF32_LE" onig-sym ; inline
: ONIG_ENCODING_EUC_JP "OnigEncodingEUC_JP" onig-sym ; inline
: ONIG_ENCODING_EUC_TW "OnigEncodingEUC_TW" onig-sym ; inline
: ONIG_ENCODING_EUC_KR "OnigEncodingEUC_KR" onig-sym ; inline
: ONIG_ENCODING_EUC_CN "OnigEncodingEUC_CN" onig-sym ; inline
: ONIG_ENCODING_SJIS "OnigEncodingSJIS" onig-sym ; inline
: ONIG_ENCODING_KOI8 "OnigEncodingKOI8" onig-sym ; inline
: ONIG_ENCODING_KOI8_R "OnigEncodingKOI8_R" onig-sym ; inline
: ONIG_ENCODING_CP1251 "OnigEncodingCP1251" onig-sym ; inline
: ONIG_ENCODING_BIG5 "OnigEncodingBIG5" onig-sym ; inline
: ONIG_ENCODING_GB18030 "OnigEncodingGB18030" onig-sym ; inline


: ONIG_SYNTAX_ASIS "OnigSyntaxASIS" onig-sym ; inline
: ONIG_SYNTAX_POSIX_BASIC "OnigSyntaxPosixBasic" onig-sym ; inline
: ONIG_SYNTAX_POSIX_EXTENDED "OnigSyntaxPosixExtended" onig-sym ; inline
: ONIG_SYNTAX_EMACS "OnigSyntaxEmacs" onig-sym ; inline
: ONIG_SYNTAX_GREP "OnigSyntaxGrep" onig-sym ; inline
: ONIG_SYNTAX_GNU_REGEX "OnigSyntaxGnuRegex" onig-sym ; inline
: ONIG_SYNTAX_JAVA "OnigSyntaxJava" onig-sym ; inline
: ONIG_SYNTAX_PERL "OnigSyntaxPerl" onig-sym ; inline
: ONIG_SYNTAX_PERL_NG "OnigSyntaxPerl_NG" onig-sym ; inline
: ONIG_SYNTAX_RUBY "OnigSyntaxRuby" onig-sym ; inline
: ONIG_SYNTAX_DEFAULT "OnigDefaultSyntax" onig-sym 0 alien-cell ; inline



LIBRARY: oniguruma

FUNCTION: int onig_new ( regex_t** reg, UChar* pattern, UChar* pattern_end, OnigOptionType option, OnigEncoding enc, OnigSyntaxType* syntax, OnigErrorInfo* err_info ) ;

FUNCTION: OnigRegion* onig_region_new ( ) ;

FUNCTION: int onig_search ( regex_t** reg, UChar* str, UChar* end, UChar* start, UChar* range, OnigRegion* region, OnigOptionType option ) ;

FUNCTION: void onig_region_free ( OnigRegion* region, int free_self ) ;

FUNCTION: void onig_free ( regex_t* reg ) ;

FUNCTION: int onig_end ( ) ;


次に libonig.factor で定義したワードを使って Factor から使いやすいワードを定義します。
./oniguruma.factor
USING: kernel io math sequences continuations prettyprint syntax
libc destructors
alien alien.c-types
oniguruma.libonig
;
IN: oniguruma

<PRIVATE

: string>uchar* ( string -- uchar* )
malloc-char-string
dup free-always
;

: (str-end-uchar*) ( uchar* length -- uchar* uchar* )
over alien-address + <alien> ;

: str-end-uchar* ( str -- uchar* uchar* )
dup string>uchar*
swap length
(str-end-uchar*) ;

: check-ret ( num -- )
ONIG_STATE_NORMAL = [ "エラーです。" throw ] unless ;

: onig-new ( str -- reg )
[ "regex_t**" <c-object> dup
rot str-end-uchar*
ONIG_OPTION_DEFAULT
ONIG_ENCODING_ASCII
ONIG_SYNTAX_DEFAULT
"OnigErrorInfo" <c-object>
onig_new
check-ret
0 alien-cell ] with-destructors ;

: (onig-region>str) ( index region -- number )
swap 4 * alien-signed-4 ;

: (onig-region-beg>str) ( region index -- number )
swap OnigRegion-beg (onig-region>str) ;

: (onig-region-end>str) ( region index -- number )
swap OnigRegion-end (onig-region>str) ;

: onig-region>str ( index string region -- substr )
rot
2dup
(onig-region-beg>str)
-rot
(onig-region-end>str)
rot subseq ;

: onig-region>str-seq ( string region -- seq )
dup OnigRegion-num_regs -rot
[ onig-region>str ] 2curry map
;

: onig-search ( reg str -- f/seq )
[ dup >r str-end-uchar* 2dup
onig_region_new dup >r
ONIG_OPTION_NONE
onig_search
r> r> rot
ONIG_NORMAL >=
[ over onig-region>str-seq ]
[ drop f ] if
swap 1 onig_region_free
] with-destructors ;

PRIVATE>

: onig ( reg str -- num )
>r onig-new dup r>
onig-search
swap
onig_free ;


最後にテストも書いておきます。ファイル名はテスト対象の ボキャブラリ名 + -tests.factor にします。
./oniguruma-tests.factor
USING: oniguruma
tools.test ;
IN: temporary

[ { "affffffffb" "ffffffff" } ]
[ "a(.*)b|[e-f]+" "zzzzaffffffffb" onig ] unit-test

[ f ] [ "x" "a" onig ] unit-test

[ { "abcde" "b" "d" } ]
[ "a(.)c(.)e" "abeeeabcdea" onig ] unit-test


これでリスナから USE: oniguruma "oniguruma" test でテストが実行されます。

FUNCTION: で C の関数定義にとても近いイメージで書けるのはいいですね。
S-STRUCT: では Common Lisp の defstruct みたいにアクセス関数が自動的に作成されるのも便利です。
しかし、スタック操作にはまだ慣れません ;-)

0 件のコメント: