「はりぼて言語」について
(1) はじめに
- 私は、「30日でできる!OS自作入門」みたいなノリで、「10日くらいでできる!プログラミング言語自作入門」を書きたいと思っています。これは書籍ではなく、webコンテンツにしようと思っています。
- それで、まずは自分でこのテキストでのゴールになりそうな言語処理系を書いてみました。そうしたら、意外に個性的でおもしろい言語処理系が短期間でできてしまいました。それでそれをここで紹介したいと思います。
- もしかしたら「プログラミング言語自作入門のテキストの最後を読むまでのお楽しみ」にするほうがいいのかもしれないですが、しかしゴールがわからないまま読み進めるのはつらいかもしれないですから、最初に「これからこんな言語を作りますよ」という説明があってもいいと思います。
(2) 基本情報 & ダウンロード
- 言語処理系の名前: 「TL-9a」(開発コードネーム)
- 名前については、「はりぼて言語」だからHL-9aとかにすればよかったです。あとでその名前に変えるかもしれません。
- いやその最初はこんなに面白い言語になるとは思っていなくて、「はりぼて」を名乗らせるつもりがなかったせいです。
- ソースコード: C言語で785行
- できるだけ機種依存しないように書いているので、多くの環境で無修正で動くのではないかと思われます。
- 私は教材用のプログラムは一般に行数が少ないことが重要だと思っています。行数が少なければ少ないほど、きっと理解もしやすいでしょう。
- もちろん行数を減らすテクニックの中には、可読性を大きく損なうようなものもあります。それはかえって理解の妨げになるので、私はそこまではやりません。
- 実行ファイルの大きさ(参考): Windows用で 10.5KB
- 私はC言語プログラムの規模を行数だけで推し量るのは賢明ではないと思っています。なぜならC言語はその気になれば1行を1000文字とかにすることもできて、そうしたら行数をすごく少なくすることだってできるからです。
- しかしそんな小手先のごまかしをしても、実行ファイルの大きさは変わりません。だから私は実行ファイルの大きさにも注目します。
- この10.5KBというサイズは、以下に説明する機能からすれば、かなり優秀なのではないかと私は自負します。
- なおこの10.5KBの中には以下のaclライブラリも静的リンクされているので、この10.5KBさえあれば以下のaclライブラリは全くなしでも問題なく実行できます。もちろん他のDLLもインクルードファイルもいりません。標準のWindowsだけでOKです。
- そういう意味では、TL-9a本体だけならもっと小さいということになります。
- きっと多くのCプログラマは、今まで10KB以上のアプリを書いたことが何度もあるでしょう。この言語はそれよりも小規模だというわけです。
- 利用ライブラリ: aclライブラリ
- 私は自作言語でグラフィックを扱うのが大好きで、この「はりぼて言語」でもグラフィック命令をいくつか持たせています。グラフィックをC言語から機種依存しないで利用するために、自作のaclライブラリを使っています。
- もしtl9a.cを自前でコンパイルする場合(Windows以外の環境で試したいとか)、aclライブラリが必要になります。aclライブラリの入手方法については以下の(6)で説明します。
- 言語のタイプ: スクリプト言語(インタプリタ)、JITコンパイラ非対応、REPL対応
- 私は高速な実行が好きで、だからJITコンパイラを作るのが好きですが、今回はJITコンパイラにはしない範囲で頑張りました。おかげでCPUアーキテクチャに依存しないで済んでいます。
- でもその分、実行速度はそれなりになります。処理内容によりますが、普通のCコンパイラ(最適化あり)と比べると5倍くらいは遅いです。
- でも5倍で済むのならむしろ非JITコンパイラとしては優秀な部類だと私は思っています。ぜひ、非JITコンパイラを自作しようとする人に参考にしてもらって、自作言語界の底上げになればいいなと思っています。
- なお、「10日くらいでできる!プログラミング言語自作入門」では、TL-9aの後に、これをJITコンパイラ化して高速化する話も書きたいと思っています。そうすれば、JITコンパイラの作成が結構容易であるとわかってもらえると思うので。
- さらに、このTL-9aをJITではない普通のコンパイラに改造する話も書きたいと思っています。これだけコンパクトなら、いろいろ応用が利くと思っているのです。
- (そもそもこのTL-9aは、内部では実行速度を上げるために内部コードへのコンパイルを行っています。だから内部コードに変換するか、x86の機械語に変換するか(JITコンパイラ)、アセンブラに変換するか(普通のコンパイラ)、内容的にはその程度の違いしかありません。)
- 基本的な機能説明: 符号付き整数型・整数演算のみ対応、演算子の優先順位は対応、ローカル変数なし、#includeも未対応、関数宣言や関数呼び出しも未対応
- 以下のプログラム例を見ると、なんだかすごくいろいろできているように見えますが、それは「見せかけ」です(笑)。#includeって書いても、なんとTL-9aはそれをコメントだと思っています(笑)。
- aMain()という、aclライブラリにおけるmain関数を書いていますが、その「void aMain() {」っていう部分もTL-9aに対しては何の意味もないコメントです。
- なぜそんな仕様になっているのかといえば、「同じアプリケーションをgccでコンパイルして実行できるようにしてみたかったから」だけです。ほんとにそれだけです、冗談だと思ってください。
- TL-9aの見た目がどんなにC言語っぽくても、TL-9aはC言語には遠く及ばないものですので、以下のサンプル以外のC言語プログラムをTL-9aに食わせてみてはいけません(笑)。いや、誤作動するところが見たければ、やっていただいて構いませんが・・・。
- なお、以下のサンプルはaclライブラリを使えば、普通にgccなどでビルドして実行することが可能です。だから以下のサンプルはC言語としては問題ないレベルになっています。
- ダウンロード: (tl9a.exeと以下のサンプルコードとtl9a.c(TL-9aのソースコード)が入っています)
(3) プログラム例 (TL-9aで実行できるプログラム)
- grd.c
- グラデーションを表示します。上記説明にある通り、#include文はTL-9aにとってはただのコメントですので、実行に際してacl.cが必要になったりはしません。
- aOpenWin(), aSetPix0(), aWait()は、TL-9aの組み込み関数で、名前をaclライブラリの同名関数と同じにしてあるだけです。
#include <acl.c>
void aMain()
{
AWindow *w;
int x, y, c;
w = aOpenWin(256, 256, "grd", 1);
c = 0;
for (y = 0; y < 256; y++) {
for (x = 0; x < 256; x++) {
aSetPix0(w, x, y, c);
c = c + 0x100;
}
}
aWait(-1);
}
- mandel.c
- マンデルブロー集合の描画プログラムです。上記説明にある通り、#include文はTL-9aにとってはただのコメントですので、実行に際してacl.cやtl9builtin.cが必要になったりはしません。
- mul64shr()という関数の処理内容が気になると思いますが、それは以下の(4)の「"tl9builtin.c"の中身」で紹介します。
#include <acl.c>
#include "tl9builtin.c"
void aMain()
{
AWindow *w;
int x, y, sx, sy, cx, cy, zx, zy, xx, yy, t, n, sn, c;
w = aOpenWin(512, 384, "mandel", 1);
for (y = 0; y < 384; y++) {
for (x = 0; x < 512; x++) {
sn = 0;
for (sx = 0; sx < 4; sx++) {
cx = (x * 4 + sx) * 56 + 4673536;
for (sy = 0; sy < 4; sy++) {
cy = (y * 4 + sy) * -56 - 124928;
zx = cx; zy = cy;
for (n = 1; n < 447; n++) {
xx = mul64shr(zx, zx, 24);
yy = mul64shr(zy, zy, 24);
t = xx + yy;
if (t > 0x4000000) break;
zy = mul64shr(zy, zx, 23);
zx = xx + cx - yy;
zy = zy + cy;
}
sn = sn + n;
}
}
n = sn >> 4;
c = aRgb8(n, 0, 0);
if (n > 255) {
c = aRgb8(0, 0, 0);
if (n != 447) {
c = aRgb8(255, n - 255, 0);
}
}
aSetPix0(w, x, y, c);
}
}
printTime();
aWait(-1);
}
- maze.c
- 迷路作成(穴掘り法)のプログラムです。上記説明にある通り、#include文はTL-9aにとってはただのコメントですので、実行に際してacl.cが必要になったりはしません。
- 乱数で迷路を作っているので、実行するたびに違う迷路が出てきます。
#include <acl.c>
void aMain()
{
AWindow *w;
int i, x, y, xx, yy, d0, d1, d2, d3, d, dd;
w = aOpenWin(752, 496, "maze", 1);
aFillRect0(w, 752, 496, 0, 0, 0x00ff00);
aFillRect0(w, 16, 16, 16, 16, 0x000000);
for (i = 0; i < 1000000; i++) {
x = ((aXorShift32() & 0x7fffffff) % 23) * 2 + 1;
y = ((aXorShift32() & 0x7fffffff) % 15) * 2 + 1;
if (aGetPix(w, x * 16, y * 16) == 0x000000) {
for (;;) {
d0 = d1 = d2 = d3 = 0;
xx = x * 16;
yy = y * 16;
aFillRect0(w, 16, 16, xx, yy, 0x000000);
if (x != 45) { d0 = (aGetPix(w, xx + 16, yy) != 0) * (aGetPix(w, xx + 32, yy) != 0); }
if (x != 1) { d1 = (aGetPix(w, xx - 16, yy) != 0) * (aGetPix(w, xx - 32, yy) != 0); }
if (y != 29) { d2 = (aGetPix(w, xx, yy + 16) != 0) * (aGetPix(w, xx, yy + 32) != 0); }
if (y != 1) { d3 = (aGetPix(w, xx, yy - 16) != 0) * (aGetPix(w, xx, yy - 32) != 0); }
d = d0 + d1 + d2 + d3;
if (d == 0) break;
dd = (aXorShift32() & 0x7fffffff) % d;
if (d0) { if (dd == 0) { aFillRect0(w, 16, 16, xx + 16, yy, 0x000000); x = x + 2; } dd = dd - 1; }
if (d1) { if (dd == 0) { aFillRect0(w, 16, 16, xx - 16, yy, 0x000000); x = x - 2; } dd = dd - 1; }
if (d2) { if (dd == 0) { aFillRect0(w, 16, 16, xx, yy + 16, 0x000000); y = y + 2; } dd = dd - 1; }
if (d3) { if (dd == 0) { aFillRect0(w, 16, 16, xx, yy - 16, 0x000000); y = y - 2; } dd = dd - 1; }
}
}
}
aWait(-1);
}
- kcube.c
- Kにとっては定番の、キューブ回転プログラムです。上記説明にある通り、#include文はTL-9aにとってはただのコメントですので、実行に際してacl.cやtl9builtin.cが必要になったりはしません。
- mul64shr()やff16sin()などの関数の処理内容が気になると思いますが、それは以下の(4)の「"tl9builtin.c"の中身」で紹介します。
#include <acl.c>
#include "tl9buitin.c"
int vertx[8] ={ 2, 2, 2, 2, 0, 0, 0, 0 };
int verty[8] ={ 2, 2, 0, 0, 2, 2, 0, 0 };
int vertz[8] ={ 2, 0, 2, 0, 2, 0, 2, 0 };
int squar[24] = { 0,4,6,2, 1,3,7,5, 0,2,3,1, 0,1,5,4, 4,5,7,6, 6,7,3,2 };
int col[6] = { 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff };
void aMain()
{
int vx[8]; int vy[8]; int vz[8];
int centerz4[6];
int scx[8]; int scy[8];
int buf0[160]; int buf1[160];
int i, j, k, l, thx, thy, thz, xp, xa, yp, ya, zp, za, xt, yt, zt, t, m;
int k0, k1, k2, e0x, e0y, e1x, e1y, l1, p0x, p0y, p1x, p1y, ymin, ymax, x, y, c;
int dx, *buf, y0, y1;
AWindow *w;
w = aOpenWin(256, 160, "kcube", 1);
for (i = 0; i < 8; i++) {
vertx[i] = (vertx[i] - 1) * 50;
verty[i] = (verty[i] - 1) * 50;
vertz[i] = (vertz[i] - 1) * 50;
}
thx = thy = thz = 0;
for (;;) {
thx = (thx + 182) & 0xffff;
thy = (thy + 273) & 0xffff;
thz = (thz + 364) & 0xffff;
xp = ff16cos(thx); xa = ff16sin(thx);
yp = ff16cos(thy); ya = ff16sin(thy);
zp = ff16cos(thz); za = ff16sin(thz);
for (i = 0; i < 8; i++) {
zt = vertz[i] * xp + verty[i] * xa;
yt = verty[i] * xp - vertz[i] * xa;
xt = vertx[i] * yp + mul64shr(zt, ya, 16);
vz[i] = mul64shr(zt, yp, 16) - vertx[i] * ya;
vx[i] = mul64shr(xt, zp, 16) - mul64shr(yt, za, 16);
vy[i] = mul64shr(yt, zp, 16) + mul64shr(xt, za, 16);
}
for (l = i = 0; i < 6; i++) {
centerz4[i] = vz[squar[l]] + vz[squar[l + 1]] + vz[squar[l + 2]] + vz[squar[l + 3]] + 0x70000000;
l = l + 4;
}
aFillRect0(w, 160, 160, 48, 0, 0x000000);
for (i = 0; i < 8; i++) {
t = (vz[i] + 13107200) >> 16;
t = 4915200 / t;
scx[i] = mul64shr(vx[i], t, 31) + 128;
scy[i] = mul64shr(vy[i], t, 31) + 80;
}
for (;;) {
m = j = 0;
for (i = 0; i < 6; i++) {
t = centerz4[i];
if (m < t) {
m = t;
j = i;
}
}
if (m == 0) break;
l = j * 4; centerz4[j] = 0;
k0 = squar[l];
k1 = squar[l + 1];
k2 = squar[l + 2];
e0x = vx[k1] - vx[k0];
e0y = vy[k1] - vy[k0];
e1x = vx[k2] - vx[k1];
e1y = vy[k2] - vy[k1];
if (mul64shr(e0x, e1y, 16) <= mul64shr(e0y, e1x, 16)) {
l1 = l + 4;
k = squar[l + 3];
p0x = scx[k]; p0y = scy[k];
ymin = 99999; ymax = 0;
for (; l < l1; l++) {
k = squar[l];
p1x = scx[k]; p1y = scy[k];
if (ymin > p1y) { ymin = p1y; }
if (ymax < p1y) { ymax = p1y; }
if (p0y != p1y) {
if (p0y < p1y) {
buf = buf0; y0 = p0y; y1 = p1y; dx = p1x - p0x; x = p0x;
} else {
buf = buf1; y0 = p1y; y1 = p0y; dx = p0x - p1x; x = p1x;
}
x = x * 65536;
dx = dx * 65536;
dx = dx / (y1 - y0);
if (dx >= 0) {
x = x + 0x8000;
} else {
x = x - 0x8000;
}
++y1;
for (y = y0; y < y1; y++) {
buf[y] = x >> 16;
x = x + dx;
}
}
p0x = p1x; p0y = p1y;
}
c = col[j];
++ymax;
for (y = ymin; y < ymax; y++) {
p0x = buf0[y];
p1x = buf1[y];
if (p0x <= p1x) {
aFillRect0(w, p1x - p0x + 1, 1, p0x, y, c);
} else {
aFillRect0(w, p0x - p1x + 1, 1, p1x, y, c);
}
}
}
}
aWait(50);
if (aInkey(w, 1) != 0) break;
}
}
- invader.c
- Kにとっては定番の、インベーダゲームのプログラムです。上記説明にある通り、#include文はTL-9aにとってはただのコメントですので、実行に際してacl.cやtl9builtin.cが必要になったりはしません。
- gprintDec()やbitblt()などの関数の処理内容が気になると思いますが、それは以下の(4)の「"tl9builtin.c"の中身」で紹介します。
#include <acl.c>
#include "tl9builtin.c"
int fght[384] = {
0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,
0,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,0,
0,1,0,0,0,1,1,1,1,1,1,0,0,1,1,1,1,1,1,0,0,0,1,0,
0,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,0,
0,1,0,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,0,1,0,
0,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
int invd[512] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,
0,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,0,
0,1,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,1,0,
0,1,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,1,0,
0,1,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,1,0,
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,
0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,
0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,
0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,
0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,
0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
void aMain()
{
int inv[192];
int i, j, k, score, pnt, mwt0, fx, idir, wunit, ix, iy, invlin, lx = 0, ly, lwt, mwt, high = 0;
AWindow *w;
w = aOpenWin(324, 228, "graphics", 1);
for (i = 0; i < 384; i++) {
fght[i] = fght[i] * 0x00ffff;
}
for (i = 0; i < 512; i++) {
invd[i] = invd[i] * 0x00ff00;
}
restart:
score = 0; pnt = 1; mwt0 = 20; fx = 18;
nxtinv:
ix = 7; iy = 1; invlin = 5;
ly = 0; lwt = 0; mwt = mwt0;
idir = 1; wunit = 1024;
for (i = 0; i < 6; i++) {
for (j = 0; j < 26; j++) {
inv[i * 32 + j] = j % 5;
}
}
for(;;) {
// 表示.
aFillRect0(w, 324, 228, 0, 0, 0x000000);
aDrawStr0(w, 34, 2, 0xffffff, 0x000000, "SCORE: HIGH:");
gprintDec(w, 82, 2, 6, 0xffffff, 0x000000, score);
gprintDec(w, 226, 2, 6, 0xffffff, 0x000000, high);
bitblt(w, 24, 16, fx * 8 + 2, 13 * 16 + 2, fght);
for (i = 0; i < 6; i++) {
for (j = 0; j < 26; j++) {
if (inv[i * 32 + j] == 1) {
bitblt(w, 32, 16, (ix + j) * 8 + 2, (iy + i) * 16 + 2, invd);
}
}
}
if (ly != 0) {
aFillRect0(w, 2, 14, lx * 8 + 5, ly * 16 + 3, 0xffff00);
}
// インベーダ全滅判定.
for (;;) {
for (i = 0; i < 26; i++) {
if (inv[invlin * 32 + i] != 0) goto skip0;
}
invlin = invlin - 1;
if (invlin < 0) {
mwt0 = mwt0 - mwt0 / 3;
aWait(1024);
goto nxtinv;
}
}
skip0:
// wait処理.
aWait(wunit);
wunit = 40;
lwt = lwt - 1;
// キー入力.
j = 0;
for (;;) {
i = aInkey(w, 1);
if (i == 0) break;
if (i == 27) goto end; // esc
if (i == 0x1024) { j = -1; }
if (i == 0x1025) { j = 1; }
if (i == 0x1026) { i = 32; }
if (i == 32) {
if (lwt <= 0) {
lwt = 15;
lx = fx + 1;
ly = 13;
}
}
}
// 自機の移動.
i = fx + j;
if (i >= 0) {
if (i <= 37) {
fx = i;
}
}
// レーザ移動.
if (ly > 0) {
ly = ly - 1;
if (ly == 0) {
pnt = pnt - 10;
if (pnt < 1) {
pnt = 1;
}
}
}
// あたり判定.
j = lx - ix;
k = ly - iy;
if (k >= 0) {
if (k <= 5) {
if (j >= 0) {
if (j < 24) {
i = inv[k * 32 + j];
if (i > 0) {
ly = 0;
j = j - i;
for (i = 0; i < 6; i++) {
inv[k * 32 + j + i] = 0;
}
score = score + pnt;
pnt++;
if (high < score) {
high = score;
}
}
}
}
}
}
// インベーダ移動.
if (mwt > 0) {
mwt = mwt - 1;
} else {
mwt = mwt0;
ix = ix + idir;
if (ix >= 0) {
if (ix <= 14) continue;
}
if (iy + invlin == 12) {
aDrawStr0(w, 122, 98, 0xff0000, 0x000000, "GAME OVER");
for (;;) {
aWait(128);
i = aInkey(w, 1);
if (i == 10) break;
if (i == 27) goto end; // esc
}
goto restart;
}
idir = idir * -1;
iy++;
ix = ix +idir;
}
}
end:
aWait(-1);
}
(4) "tl9builtin.c"の中身
- tl9builtin.cは、上記プログラム例をgccなどの普通のコンパイラでコンパイルするときにのみ必要になるファイルで、TL-9aで実行するときには不要です。
- TL-9aの組み込み関数の処理に相当する処理が書いてあります。
static inline int mul64shr(int i,int j, int k)
{
return ((long long) i) * ((long long) j) >> k;
}
static inline void printTime()
{
printf("time: %.3f[sec]\n", clock() / (double) CLOCKS_PER_SEC);
}
static inline int ff16sin(int x)
{
return (int) (sin(x * (2 * 3.14159265358979323 / 65536)) * 65536);
}
static inline int ff16cos(int x)
{
return (int) (cos(x * (2 * 3.14159265358979323 / 65536)) * 65536);
}
void gprintDec(AWindow *w, int x, int y, int l, int c, int b, int i)
{
char s[100];
sprintf(s, "%*d", l, i);
aDrawStr0(w, x, y, c, b, s);
}
void bitblt(AWindow *w, int sx, int sy, int x, int y, int *p)
{
AInt32 *q = &w->buf[x + y * w->xsiz];
int i, j;
for (j = 0; j < sy; j++) {
for (i = 0; i < sx; i++) {
q[i] = p[i];
}
p += sx;
q += w->xsiz;
}
}
(5) 感想
- 私は2020年にES-BASICを作りました。これはBASIC言語+アセンブラ+デバッグ支援機能+JITコンパイラ+普通のコンパイラで44.0KB(Windows版)というもので、これはいいものを作ったなーと自画自賛していました。しかしソースコードは3160行もあり、それなりの規模はあります。
- 今回のTL-9aは、これに対して785行で10.5KBです。ES-BASICは演算子の優先順位を考慮せず演算は常に左から右でしたが(括弧を書いても無視される)、TL-9aはC言語の見た目を目指したので、ちゃんと優先順位が反映されます。だからES-BASICと比べてすべてにおいて負けているというわけでもないのです。
- いや、なんというかC言語プログラムがそのまま10.5KBの処理系で実行できてしまうところに、私は衝撃を感じました。C言語ってこんなに簡単な言語だったっけ?と。もちろん様々な制限を導入して、だから小さく作れているわけですが、しかしそれでも「プログラム例」をみたら、これがたった10.5KBで動かせるっていうのは、やっぱり衝撃だと思うのです。・・・そもそもC言語でプログラムを書けば、超簡単な49行のHL-1でさえも、ランタイムルーチンなどで4.0KBにはなるのです。そこから6.5KBしか増えなくて、それでここまでできるようになってしまうなんて・・・。
- TL-9aはソースコード行数を減らして見通しをよくするために、エラー処理などはかなり省いてあります。だからこのまま常用できるようなレベルではありません。しかしまあ、エラー処理を書き足すくらいなら、それほど大変ではないでしょう(しかし結果として10.5KBを超えてしまうだろうとは思いますが)。
- ちなみに今回使った式解釈アルゴリズムや構文解釈アルゴリズムは、C言語のみならずもっといろいろな言語に対しても十分に適用可能だと思います。C言語文法だけが特別なのではありません。
- さらにTL-9aに数十行書き足せば、関数宣言や関数呼び出しができるようになるだろうとは思っていますが、今回のプログラム例で関数の機能が必要にはならなかったので、実装は見送りました。
(6) aclライブラリの入手方法
- Windowsの場合: (Windows専用版を使う方法と、SDL2.0対応版を使う方法があります。)
- aclib21(Windows専用版をMinGW-5.1.6で使う例)
- aclib07(SDL2.0対応版をMinGW-5.1.6で使う例)
- MacOSの場合:
- Linuxの場合:
- Androidの場合: (Android 7.0以降が必要です。スマートフォンでもタブレットでもOKです。)
- aclib08 (SDL2.0対応版をTermux+clangで使う例)
- ラズベリーパイの場合:
- 「はりぼてOS」の場合:
- その他(SDL2.0とCの標準ライブラリを使った一般的なやり方):
こめんと欄
|