mcommit's message

ソフトウェア開発の仕事をしているsimotinといいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

C言語でもevaりたい 〜コードなエッセイを読んだ〜

立ち読みだけにするつもりがついつい買ってしまいました。

小飼弾のコードなエッセイ。

最初のエッセイでカッコつきの四則演算をするプログラムを書こうという話が登場していました。

入力:(1 + 3) * 3
出力:12

みたいなことをやるプログラムをかけということです。
入力は当然文字列となります。

エッセイ自体は、

Rubyperlpythonではevalが使えるから簡単だよ。evalすごいよ。

みたいな話だったのですが、

C言語で私もevaりたい!と思い、evaったコードを書いてみました。

■eval.c

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

int main( int argc, char **argv ) {
    FILE *fp;
    void *handle;

    if ( argc != 2 ) {
        fprintf( stderr, "Args Error\n" );
        return 0;
    }

    fp = fopen( "tmp.c", "w" );

    fprintf( fp, "int calc(void){return (%s);}", argv[1] );
    fclose( fp );

    system( "gcc tmp.c -shared -o tmp.so" );

    handle = dlopen( "./tmp.so", RTLD_LAZY );
    int(*calc)(void) = dlsym( handle, "calc" );
    fprintf( stdout, "%d\n", (*calc)() );

    dlclose( handle );
    return 0;
}

evaり具合はこんな感じです。
※エラーチェックはしていません。

実行させてみます。


$gcc -Wall eval.c -ldl
$./a.out "(1 + 2) * 3"
9
コマンドラインで実行するときは数式はダブルコーテーション"で囲ってください。

どうです!?

evaれてるでしょ。(・´ー・`)どや?

system関数使っている時点で反則感は漂いますが、gccC言語ということで。
何が言いたかったかと言うと、

「dlopen,dlsyncを使った動的ロード」

を使えばC言語でもevalっぽいことはできるよってことです。

ちなみにこの動的ロードはRubyでも拡張ライブラリをrequireする際に使われています。
このへんですね。

余談ですがRubyC言語を美しく抽象化していると思えて仕方がないのです。好きです。Ruby...

evaりたいと言って非正攻なやり方でカッコつきの文字列を計算するという問題を解いてみましたが、
正攻法の方は難しいです。
暇な時にちょっと考えてみたのですが、真面目に実装すると面倒臭いです。

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

int calc(unsigned char sym, int value, int *result);
int parse(char *str, int length);

int main(int argc, char** argv) {
    int result = 0;
    if ( argc != 2) {
        fprintf(stderr, "args error\n");
        return -1;
    }
    result = parse( argv[1], strlen( argv[1] ) );
    fprintf( stdout, "%d\n", result );
    return 0;
}

int parse(char *str, int length) {
    int bracketsFound = 0;
    int numFound = 0;
    int len = 0;
    int val = 0;
    char *endptr;
    char *pNum,*bracketStart;
    char sym = '+';
    char tmp[64];
    int i, result;

    result = 0;

    for ( i = 0; i < length; i++ ) {
        switch ( str[ i ] ) {
        case '(':
            if (!bracketsFound) {
                bracketsFound = 1;
                bracketStart = &str[ i ];
            } else {
                /* TODO */
            }
            break;
        case ')':
            if (bracketsFound) {
                bracketsFound = 0;
                numFound = 0;
                /* ここまでを計算する */
                val = parse( bracketStart+1, (&str[ i ] - (bracketStart+1)) );
                calc(sym, val, &result);
            } else {
                return -1;
            }
            break;
        case ' ':
            /* skip */
            break;
        case '+':
        case '-':
        case '/':
        case '*':
            if (!bracketsFound) {
                if ( numFound ) {
                    numFound = 0;
                    strncpy( tmp, pNum, len );
                    val = strtol( tmp, &endptr, 10 );
                    calc(sym, val, &result);
                    sym = str[ i ];
                } else {
                    sym = str[ i ];
                }
            }
            numFound = 0;
            break;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            if ( !numFound ) {
                numFound = 1;
                len = 1;
                pNum = &str[ i ];
            } else {
                len++;
            }
            break;
        default:
            return -1;
        }
    }

    if ( numFound ) {
        strncpy( tmp, pNum, len );
        val = strtol( tmp, &endptr, 10 );
        calc( sym, val, &result );
    }

    return result;
}

int calc(unsigned char sym, int value, int *result) {

    switch (sym) {
    case '+':
        *result += value;
        break;
    case '-':
        *result -= value;
        break;
    case '/':
        *result /= value;
        break;
    case '*':
        *result *= value;
        break;
    default:
        return -1;
    }

    return 0;
}

※上記コードはちゃんと動かないケースもあるし、演算子の優先順位に対応できていません。

リファクタリングですが、それにしても長い!
※後で思ったのですが自力でガリガリ書くよりflexとbisonを使った方が早いですね。

いつも思うのですが、

「〇〇パーサー」

みたいなのって実装するのが面倒ですね。

このへんの実装は仕事ではやりたくないなというのが正直な気持ちです。

コードなエッセイ、文字通りコードをベースにしたエッセイ集で「弾言」いっぱいの面白い一冊でした。

追記

上記の「電卓」プログラムですが勉強してみてもう少しまともなプログラムを書いてみたので記事を書きました。
文字列を解析する際にはそれなりの作法というか定石があるんですね・・・

mcommit.hatenadiary.com