立ち読みだけにするつもりがついつい買ってしまいました。
小飼弾のコードなエッセイ。
最初のエッセイでカッコつきの四則演算をするプログラムを書こうという話が登場していました。
入力:(1 + 3) * 3
出力:12
みたいなことをやるプログラムをかけということです。
入力は当然文字列となります。
エッセイ自体は、
Rubyやperlやpythonでは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関数使っている時点で反則感は漂いますが、gccもC言語ということで。
何が言いたかったかと言うと、
「dlopen,dlsyncを使った動的ロード」
を使えばC言語でもevalっぽいことはできるよってことです。
ちなみにこの動的ロードはRubyでも拡張ライブラリをrequireする際に使われています。
このへんですね。
余談ですがRubyはC言語を美しく抽象化していると思えて仕方がないのです。好きです。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を使った方が早いですね。
いつも思うのですが、
「〇〇パーサー」
みたいなのって実装するのが面倒ですね。
このへんの実装は仕事ではやりたくないなというのが正直な気持ちです。
小飼弾のコードなエッセイ ~我々は本当に世界を理解してコードしているのだろうか? (Software Design plus)
- 作者: 小飼弾
- 出版社/メーカー: 技術評論社
- 発売日: 2013/04/16
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (5件) を見る
コードなエッセイ、文字通りコードをベースにしたエッセイ集で「弾言」いっぱいの面白い一冊でした。
追記
上記の「電卓」プログラムですが勉強してみてもう少しまともなプログラムを書いてみたので記事を書きました。
文字列を解析する際にはそれなりの作法というか定石があるんですね・・・