VTL (Very Tiny Language)の実装を七行プログラミングで挑戦してみました
1970年代の中盤に開発された極小インタプリタ言語 VTL (Very Tiny Language) の基本的な演算構文を7行で書いてみました。
VTL に関しては、以下に詳しい記述があります。
一つめのリンクで公開されている実装とほぼ同じ挙動をするものを C# で書いてみましたが、こういう小さな実装はポインタをガリガリ動かしながら式を解釈していくのが面白そうだと思い、C で七行プログラミングに挑戦してみました。
コード
結果からいうと、自分の力ではプログラミングモードや GOTO を7行内に実装することができませんでした。次のコードは、ダイレクトモードで VTL の基本演算処理を行うものです(これは一応7行)。
vtl7.c (Gist)
#include <stdio.h>
#include <stdlib.h>
char c[80];int v[25];int G(char*p){char*q=p;int n=(int)strtol(p,&p,0);return q-
p?n:v[*p++-65];}int E(char*p){int n=G(p);for(;*p;){char o=*p++;int m=G(p);if(o
==43)n+=m;if(o==45)n-=m;if(o==42)n*=m;if(o==47)n/=m;if(o==60)n=n<=m;if(o==61)n=
n==m;if(o==62)n=n>=m;}return n;}P(){printf("OK\n* ");}int main(){P();for(;gets(
c);){c[1]-61||*c-63?v[*c-65]=E(c+2):printf("%d\n",E(c+2));P();}return 0;}
ビルドと実行
※ 七行プログラミングということもあり、エラーチェックなどはほぼ省いてあります。変な値を入力するとすぐにセグフォ吐いて死にますので、自己責任で実行してください。
gcc -o vtl7 vtl7.c
./vtl7
本家 VTL は実装の小ささが売りです。手元の環境の 32 bit Debian + GCC 4.7.2 で -Os をつけて strip したところ、3.7 キロバイトでした。うちの子はそこまで小さくないけど、昨今の ELF 形式ならこんなもんかな…?
実行すると、対話型シェル(というほどたいそうなものではありませんが…)が起動します。
OK
* A=123
OK
* B=2
OK
* ?=A*B+4
250
OK
*
上の例では、変数 A に 123 を、変数 B に 2 を代入し、A*B+4 の式を計算して印字しています。 なお、OK だろうが ERR だろうが、OK と出てきます。本当にエラーが発生した場合は「Segmentation fault」と出て死にます。こわい。
シェルを終了するには、C-d を押します。
サポートしているもの
- 変数 …
AからZの25文字が使えます。 - 演算子 … 二項演算子
+,-,*,/に加え、比較演算子=,>,<が使えます。 - 代入構文 …
A=100やB=A*2のように変数に値や式の評価結果を代入することができます。 - 印字 …
?=Aや?=1+2+3のように変数の内容や式の評価結果を端末に印字することができます。
また、
- 任意の場所に空白文字を入れることはできません。
- 2文字目が
=ではないものは、すべてコメントとして読み飛ばされます。 - 演算の優先順位はなく、式を左から解釈していきます。
1+2*3は7ではなく、9になります。 - 比較演算子を式に用いると、真の時は
1を返し、偽の時は0を返します。 - 比較演算子の
>,<はそれぞれ、C でいう>=,<=として解釈されます。 - 式の中に負数は使えませんが、
A=0-100とすると変数Aに-100を代入でき、式で使えます。 - 端末からの入力、文字の印字、配列やジャンプ構文などは実装していません。
- 浮動小数点数はサポートしていません。また、剰余
%もサポートしません。
という感じです。
ジャンプ構文がないと、ただの「変数が使える電卓」レベルですね。 あと4、5行余裕があれば実装できるかもしれません。
圧縮前のコード
元のコードも一応貼りつけておきます。
vtl.c (Gist)
#include <stdio.h>
#include <stdlib.h>
char c[80];
int v['Z'-'A'] = {0};
// 数値・値を解釈して取得
int getval(char* p)
{
char* q = p;
int a = (int)strtol(p, &p, 0);
if (q == p) {
p++;
return v[*q-'A'];
}
return a;
}
// 演算
int expr(char* p)
{
int a = getval(p);
for (;*p;) {
char op = *p++;
int b = getval(p);
if (op == '+')
a += b;
if (op == '-')
a -= b;
if (op == '*')
a *= b;
if (op == '/')
a /= b;
if (op == '=')
a = a==b;
if (op == '>')
a = a >= b;
if (op == '<')
a = a <= b;
}
return a;
}
#define DEBUG
int main()
{
#ifdef DEBUG
int i;
#endif
printf("OK\n* ");
for (;gets(c);) {
if (c[1] == '=') {
if (*c == '?') {
printf("%d\n", expr(c + 2));
}
else {
v[*c-'A'] = expr(c + 2);
}
}
printf("OK\n* ");
}
#ifdef DEBUG
for (i=0; i < 'Z' - 'A'; i++)
printf("v[%d] = %d\n", i, v[i]);
#endif
return 0;
}
gets() を使っているところは論外ですが、getval() 内の strtol() の引数 p と &p も本来ならばやっちゃいけない投げ方だと思います。
strtol() は文字列を数値にするのはもちろんですが、演算子と数値部分のパースに使っています。 ポインタ p が進んでいれば数値化もできて文字列の式もその分だけパース完了、進んでいなければ *p は変数名であるという解釈をしています。