- 追加された行はこの色です。
- 削除された行はこの色です。
* 川合のプログラミング言語自作のためのテキスト第三版#9
-(by [[K]], 2021.03.19)
** (1) HL-9
-今回は、HL-8aをaclライブラリを利用できるように書き換えた後で、いくつかのグラフィック命令を追加します。
-追加の命令は以下の通りです。
|タイプ|形式|説明|HL-9aのどのサンプルで使っているか?|
|関数|mul64shr(a, b, c)|a * b を64bitで計算して、結果を >> c したものを返す。|mandel, kcube|
|関数|aRgb8(r, g, b)|0~255のRGB値から、色番号を得る。|mandel|
|関数|aOpenWin(sx, sy, t, dmy)|グラフィックウィンドウを開く。dmyは参照されない(同名のC関数の真似をしたかっただけ)。|mandel, maze, kcube, invader|
|関数|aXorShift32()|32bit符号付き整数の乱数を返す。|maze|
|関数|aGetPix(win, x, y)|グラフィックウィンドウ上のピクセルの色番号を取得する。winは参照されない。|maze|
|関数|ff16sin(t)|角度は1周を65536度とする。そして結果は65536倍して、整数で返す。そういうsin。|kcube|
|関数|ff16cos(t)|角度は1周を65536度とする。そして結果は65536倍して、整数で返す。そういうcos。|kcube|
|関数|aInkey(dmy, mod)|グラフィックウィンドウに入力されたキー入力を取得する。通常はmod=1で使う。|invader|
|命令|aSetPix0(win, x, y, col)|グラフィックウィンドウの座標(x, y)のピクセルの色をcolに設定する。|mandel|
|命令|aWait(msec)|指定した秒数(ミリ秒)だけ待つ。|mandel, maze, kcube, invader|
|命令|aFillRect0(win, xsiz, ysiz, x, y, c)|グラフィックウィンドウに塗りつぶした長方形を描画する。|maze, kcube, invader|
|命令|aDrawStr0(win, x, y, col, bcol, str)|グラフィックウィンドウに文字列を描画する。|invader|
|命令|gprintDec(win, x, y, len, col, bcol, i)|グラフィックウィンドウに整数iを描画する。|invader|
|命令|bitblt(win, xsiz, ysiz, x, y, ary)|配列ary[]に入ったカラーコード列を、指定された長方形範囲に転送する。|invader|
-このうち、mul64shr()とff16sin()とff16cos()は、整数演算しかできないHL-9で、固定小数点演算を支援するためのものです。
-aclライブラリでは、intptr_tの代わりにAIntを使うのが標準的なので、書き換えています。
-aclライブラリでは、main()関数をvoid aMain()に書き換える必要があります。
--argcやargvはaArgc, aArgvで参照できます。
-aclライブラリは標準ライブラリのincludeを含むので、プログラム中でのincludeを省略できます。
#include <acl.c>
typedef unsigned char *String; // こう書くと String は unsigned char * の代用になる.
int loadText(String path, String t, int siz) → HL-4と同じなので省略
///////////////////////////////////////////////////////////////////////////////
#define MAX_TC 1000 // トークンコードの最大値.
String ts[MAX_TC + 1]; // トークンの内容(文字列)を記憶.
int tl[MAX_TC + 1]; // トークンの長さ.
unsigned char tcBuf[(MAX_TC + 1) * 10]; // トークン1つ当たり平均10バイトを想定.
int tcs = 0, tcb = 0;
AInt var[MAX_TC + 1]; // 変数. (!)
int getTc(String s, int len) → HL-8aと同じなので省略
///////////////////////////////////////////////////////////////////////////////
int isAlphabetOrNumber(unsigned char c) → HL-2と同じなので省略
int lexer(String s, int tc[]) → HL-8aと同じなので省略
int tc[10000]; // トークンコード.
enum { TcSemi = 0, TcDot, TcWiCard, Tc0, Tc1, Tc2, Tc3, Tc4, Tc5, Tc6, Tc7, Tc8, TcBrOpn, TcBrCls, TcSqBrOpn, TcSqBrCls, TcCrBrOpn, TcCrBrCls,
TcEEq, TcNEq, TcLt, TcGe, TcLe, TcGt, TcPlus, TcMinus, TcAster, TcSlash, TcPerce, TcAnd, TcShr, TcPlPlus, TcEqu,
TcComma, TcExpr, TcExpr0, TcTmp0, TcTmp1, TcTmp2, TcTmp3, TcTmp4, TcTmp5, TcTmp6, TcTmp7, TcTmp8, TcTmp9 };
char tcInit[] = "; . !!* 0 1 2 3 4 5 6 7 8 ( ) [ ] { } == != < >= <= > + - * / % & >> ++ = , !!** !!*** _t0 _t1 _t2 _t3 _t4 _t5 _t6 _t7 _t8 _t9";
///////////////////////////////////////////////////////////////////////////////
int phrCmp_tc[32 * 100], ppc1, wpc[9], wpc1[9]; // ppc1:一致したフレーズの次のトークンをさす, wpc[]:ワイルドカードのトークンの場所をさす.
int phrCmp(int pid, String phr, int pc) → HL-7と同じなので省略
///////////////////////////////////////////////////////////////////////////////
typedef AInt *IntP; // こう書くと IntP は AInt * の代わりに使えるようになる. (!)
enum { OpCpy = 0, OpCeq, OpCne, OpClt, OpCge, OpCle, OpCgt, OpAdd, OpSub, OpMul, OpDiv, OpMod, OpAnd, OpShr,
OpAdd1, OpNeg, OpGoto, OpJeq, OpJne, OpJlt, OpJge, OpJle, OpJgt, OpLop, OpPrint, OpTime, OpEnd,
! OpPrints, OpAryNew, OpAryInit, OpArySet, OpAryGet, OpOpnWin, OpSetPix0, OpM64s, OpRgb8, OpWait,
+ OpXorShift, OpGetPix, OpFilRct0, OpPrm, OpF16Sin, OpF16Cos, OpInkey, OpDrwStr0, OpGprDec, OpBitBlt };
IntP ic[10000], *icq; // ic[]:内部コード、icq:ic[]への書き込み用ポインタ.
void putIc(int op, IntP p0, IntP p1, IntP p2, IntP p3) → HL-6と同じなので省略
///////////////////////////////////////////////////////////////////////////////
char tmp_flag[10]; // 一時変数の利用状況を管理.
int tmpAlloc() → HL-7と同じなので省略
void tmpFree(int i) → HL-7と同じなので省略
///////////////////////////////////////////////////////////////////////////////
int epc, epc1; // exprのためのpcとpc1.
int exprSub(int priority); // exprSub1()が参照するので、プロトタイプ宣言.
int expr(int j);
int phrCmpPutIc(int pid, String phr, int pc, int *pi, int lenExpr, int op, int *err);
int exprSub1(int i, int priority, int op) → HL-7と同じなので省略
int exprSub(int priority)
{
int i = -1, e0 = 0, e1 = 0;
ppc1 = 0;
(中略)
} else if (tc[epc] == TcMinus) { // 単項マイナス.
epc++;
e0 = exprSub(2);
i = tmpAlloc();
putIc(OpNeg, &var[i], &var[e0], 0, 0);
+ } else if (phrCmpPutIc(72, "mul64shr(!!**1, !!**2, !!**3)", epc, &i, 4, OpM64s, &e0)) {
+ } else if (phrCmpPutIc(73, "aRgb8(!!**1, !!**2, !!**3)", epc, &i, 4, OpRgb8, &e0)) {
+ } else if (phrCmpPutIc(74, "aOpenWin(!!**0, !!**1, !!***2, !!***8)", epc, 0, 3, OpOpnWin, &e0)) {
+ i = Tc0;
+ } else if (phrCmpPutIc(75, "aXorShift32()", epc, &i, 1, OpXorShift, &e0)) {
+ } else if (phrCmpPutIc(76, "aGetPix(!!**8, !!**1, !!**2)", epc, &i, 3, OpGetPix, &e0)) {
+ } else if (phrCmpPutIc(77, "ff16sin(!!**1)", epc, &i, 2, OpF16Sin, &e0)) {
+ } else if (phrCmpPutIc(78, "ff16cos(!!**1)", epc, &i, 2, OpF16Cos, &e0)) {
+ } else if (phrCmpPutIc(79, "aInkey(!!***8 , !!**1)", epc, &i, 2, OpInkey, &e0)) {
} else { // 変数もしくは定数.
i = tc[epc];
epc++;
}
(中略)
}
int expr(int j) → HL-7と同じなので省略
///////////////////////////////////////////////////////////////////////////////
enum { IfTrue = 0, IfFalse = 1 };
void ifgoto(int i, int not, int label) → HL-8と同じなので省略
int tmpLabelNo;
int tmpLabelAlloc() → HL-8と同じなので省略
#define BInfSiz 10
int binf[BInfSiz * 100], bd, lbd; // binf:block-info, bd:block-depth, lbd:loop-block-depth
enum { BlkIf = 1, BlkFor };
enum { IfLabel0 = 1, IfLabel1 };
enum { ForLopBgn = 1, ForCont, ForBrk, ForLbd0, ForWpc01, ForWpc11, ForWpc02, ForWpc12 };
int phrCmpPutIc(int pid, String phr, int pc, int *pi, int lenExpr, int op, int *err) // この関数はHL-9で追加したもの.
{
int e[9], i, i0 = 0;
if (phrCmp(pid, phr, pc)) {
e[0] = e[1] = e[2] = e[3] = e[4] = e[5] = e[6] = e[7] = e[8] = 0;
if (pi != 0) {
e[0] = *pi = tmpAlloc();
i0 = 1;
}
for (i = i0; i < lenExpr; i++) {
e[i] = expr(i);
}
putIc(op, &var[e[0]], &var[e[1]], &var[e[2]], &var[e[3]]);
if (lenExpr >= 5) {
putIc(OpPrm, &var[e[4]], &var[e[5]], &var[e[6]], &var[e[7]]);
}
for (i = i0; i < lenExpr; i++) {
if (e[i] < 0) {
*err = -1;
}
tmpFree(e[i]);
}
return 1;
}
return 0;
}
///////////////////////////////////////////////////////////////////////////////
int compile(String s)
{
(中略)
} else if (phrCmp(22, "int !!*0[!!**2] = {", pc)) {
(中略)
+ } else if (phrCmpPutIc(23, "aSetPix0(!!***8, !!**0, !!**1, !!**2);", pc, 0, 3, OpSetPix0, &e0)) {
+ } else if (phrCmpPutIc(24, "aWait(!!**0);", pc, 0, 1, OpWait, &e0)) {
+ } else if (phrCmpPutIc(25, "aFillRect0(!!***8, !!**0, !!**1, !!**2, !!**3, !!**4);", pc, 0, 5, OpFilRct0, &e0)) {
+ } else if (phrCmpPutIc(26, "aDrawStr0(!!***8, !!**0, !!**1, !!**2, !!**3, !!**4);", pc, 0, 5, OpDrwStr0, &e0)) {
+ } else if (phrCmpPutIc(27, "gprintDec(!!***8, !!**0, !!**1, !!**2, !!**3, !!**4, !!**5);", pc, 0, 6, OpGprDec, &e0)) {
+ } else if (phrCmpPutIc(28, "bitblt(!!***8, !!**0, !!**1, !!**2, !!**3, !!**4);", pc, 0, 5, OpBitBlt, &e0)) {
} else if (phrCmp( 8, "!!***0;", pc)) { // これはかなりマッチしやすいので最後にする.
e0 = expr(0);
(中略)
}
void exec()
{
clock_t t0 = clock();
IntP *icp = ic;
! AInt i, j, *a, sx, sy;
+ AInt32 *p32;
+ char s[100];
for (;;) {
switch ((int) icp[0]) {
(中略)
+ case OpOpnWin:
+ if (win != 0) {
+ if (win->xsiz < *icp[1] || win->ysiz < *icp[2]) {
+ printf("openWin error\n");
+ return;
+ }
+ } else
+ win = aOpenWin(*icp[1], *icp[2], (char *) *icp[3], 0);
+ icp += 5;
+ continue;
+ case OpSetPix0:
+ aSetPix0(win, *icp[1], *icp[2], *icp[3]);
+ icp += 5;
+ continue;
+ case OpM64s:
+ *icp[1] = (((AInt64) *icp[2]) * ((AInt64) *icp[3])) >> *icp[4];
+ icp += 5;
+ continue;
+ case OpRgb8:
+ *icp[1] = aRgb8(*icp[2], *icp[3], *icp[4]);
+ icp += 5;
+ continue;
+ case OpWait:
+ if (*icp[1] == -1) {
+ if (win != 0)
+ aFlushAll(win);
+ return;
+ }
+ aWait(*icp[1]);
+ icp += 5;
+ continue;
+ case OpXorShift:
+ *icp[1] = aXorShift32();
+ icp += 5;
+ continue;
+ case OpGetPix:
+ *icp[1] = aGetPix(win, *icp[2], *icp[3]);
+ icp += 5;
+ continue;
+ case OpFilRct0:
+ aFillRect0(win, *icp[1], *icp[2], *icp[3], *icp[4], *icp[6]);
+ icp += 10;
+ continue;
+ case OpF16Sin:
+ *icp[1] = (AInt) (sin(*icp[2] * (2 * 3.14159265358979323 / 65536)) * 65536);
+ icp += 5;
+ continue;
+ case OpF16Cos:
+ *icp[1] = (AInt) (cos(*icp[2] * (2 * 3.14159265358979323 / 65536)) * 65536);
+ icp += 5;
+ continue;
+ case OpInkey:
+ *icp[1] = aInkey(win, *icp[2]);
+ icp += 5;
+ continue;
+ case OpDrwStr0:
+ aDrawStr0(win, *icp[1], *icp[2], *icp[3], *icp[4], (char *) *icp[6]);
+ icp += 10;
+ continue;
+ case OpGprDec:
+ sprintf(s, "%*d", *icp[3], *icp[7]);
+ aDrawStr0(win, *icp[1], *icp[2], *icp[4], *icp[6], s);
+ icp += 10;
+ continue;
+ case OpBitBlt:
+ a = (AInt *) *icp[6];
+ p32 = &win->buf[*icp[3] + *icp[4] * win->xsiz];
+ sx = *icp[1];
+ sy = *icp[2];
+ for (j = 0; j < sy; j++) {
+ for (i = 0; i < sx; i++) {
+ p32[i] = a[i];
+ }
+ a += sx;
+ p32 += win->xsiz;
+ }
+ icp += 10;
+ continue;
}
}
}
int run(String s) → HL-6と同じなので省略
///////////////////////////////////////////////////////////////////////////////
void aMain() // 関数名を変更.
{
unsigned char txt[10000];
int i;
lexer(tcInit, tc);
! if (aArgc >= 2) {
! if (loadText((String) aArgv[1], txt, 10000) == 0) {
run(txt);
}
exit(0);
}
for (;;) { // Read-Eval-Print Loop.
(中略)
}
}
-プログラムは754行になりました。でもやっていることは、組み込み関数が増えただけです。
-私としては、「組み込み関数を増やすのは簡単」だということと、「ちょっと組み込み関数を増やしてやるだけで、自作言語でできることはすごく増える」の二点を実感してほしいです。
--組み込み関数を1つ増やすためには、enumに内部コード番号を一つ追加して、exprSub()かcompile()に1行追加して、exec()のcase文に処理内容を書くだけです。とてもシンプルです。
----
-(以下準備中)
-HL-8aにはprints命令があります。
>prints "hello"
hello
-まあ、それだけなのですが・・・。まあ一応、以下みたいなこともできます。
a = "abc";
if (x > y) {
a = "def";
-このHL-9は例えば以下のプログラムを実行できます。
win = aOpenWin(256, 256, "gradation", 1);
for (y = 0; y < 256; y++) {
for (x = 0; x < 256; x++) {
aSetPix0(win, x, y, aRgb8(y, x, 0));
}
}
prints a;
-http://k.osask.jp/files/pic20200917a.png
-これが動くのは当たり前だし当然なのですが、しかしそれでもこれがこんなに書いた通りに動いてくれると、「なんかまともになったなあ」と感じます。
-HL-8aでは配列も使えます。
>int a[30] = { 1, 1 }
>for (i = 2; i < 30; i++) { a[i] = a[i - 2] + a[i - 1]; }
>for (i = 0; i < 10; i++) { print a[i]; }
1
1
2
3
5
8
13
21
34
55
-他にも、mandelやmazeやkcubeやinvaderが動くのですが、今それをここで書くと、次のHL-9aと重複してしまって冗長なので、ここでは省略します。
-(以下準備中)
** (2) HL-8aの簡単な説明
-基本的なことは、(1)の最初にある程度書いたので、この節では省略します。
-細かいことを書きます。
--文字列リテラルでは、エスケープシーケンスを処理していません。だから改行(\n)などを書いてもうまくいきません。
--配列では、宣言されるたびにmalloc()でメモリを確保しています。そして使い終わってもmalloc()したものをfree()しません。
--だからどんどんメモリリークします。これをどうするか考えたのですが、まあHLシリーズとしては「HL-9aのデモが動けばそれでいい」くらいの言語なので、ひとまずリークしたままにすることにしました。
** (3) HL-8aにおける配列アクセスの実現方法
-何も考えずに添え字演算子を実装するとこんな感じになると思います。
} else if (phrCmp(71, "[!!**0]", epc)) {
e1 = i;
i = tmpAlloc();
e0 = expr(0);
putIc(OpAryGet, &var[e1], &var[e0], &var[i], 0);
epc = ppc1;
-配列から値を読むだけなら、これで何の問題もなく十分に機能します。
-しかし実際には、 a[i] = 3; みたいに配列への代入をすることがあり得ます。そうすると、上記の方法では、tmpAlloc()された変数の値が更新されるだけで、a[i]の値を変えることができません。
-ということで、添え字演算子の直後に代入演算子が来た時に限って、特別な処理をすることにしました。
-なお、これは上記のphrCmp()よりも先にやらないと、先に上記のほうが一致してしまって処理を奪われてしまうので、先に記述します。
} else if (phrCmp(70, "[!!**0]=", epc) && priority >= 15) {
e1 = i;
e0 = expr(0);
epc = ppc1;
i = exprSub(15);
putIc(OpArySet, &var[e1], &var[e0], &var[i], 0);
-こうすることで、a[i] = 3;で配列の値が更新されるようになります。
** 次回に続く
-次回: ''a21_txt01_9''
*こめんと欄
#comment