- 追加された行はこの色です。
- 削除された行はこの色です。
* 川合のプログラミング言語自作のためのテキスト第三版#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)||kcube|
|関数|ff16cos(t)||kcube|
|関数|ff16sin(t)|角度は1周を65536度とする。そして結果は65536倍して、整数で返す。そういうsin。|kcube|
|関数|ff16cos(t)|角度は1周を65536度とする。そして結果は65536倍して、整数で返す。そういうcos。|kcube|
|関数|aInkey(dmy, mod)|グラフィックウィンドウに入力されたキー入力を取得する。通常はmod=1で使う。|invader|
|命令|aSetPix0()||mandel|
|命令|aWait()||mandel, maze, kcube, invader|
|命令|aFillRect0()||maze, kcube, invader|
|命令|aDrawStr0()||invader|
|命令|gprintDec()||invaer|
|命令|bitblt()||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を省略できます。
-(以下準備中)
-今回は、配列と文字列リテラルのサポートです。
-もう少し詳しく言うと、こんな感じです。
--[1]変数の値(var[])の型をintではなく、intptr_tにする。
---こうすることで、変数にポインタの値を入れることもできるようになります。それを使って配列や文字列リテラルをサポートします。
---intのままでも32bitなら問題ないけど、x64などのようにintとポインタの幅が異なる環境ではうまくいかなくなるので、一般的にはintptr_tにしたほうがよいのです。
--[2]getTc()やlexer()を改造して、文字列リテラルを受け付けるようにする。
---そしてcompile()にprints命令を追加して、exec()にOpPrintsを追加して、文字リテラルを表示できるようにします。
--[3]配列については、OpArySet, OpAryGet, OpAryNew, OpAryInitという内部命令を追加して、配列の操作をする。exprSub()とcompile()とexec()を改造して対応させる。
|icp[0]|icp[1]|icp[2]|icp[3]|icp[4]|動作|説明|
|OpPrints |p1| | | |printf("%s\n", p1);|文字列の表示|
|OpAryNew |p1|p2| | |p1=new intptr_t[p2]; (malloc)|配列の準備|
|OpAryInit|p1|p2|p3| |memcpy(p1,p2,p3*sizeof(inttr_t));|配列への初期値代入|
|OpArySet |p1|p2|p3| |p1[p2]=p3;|変数の値を配列へ代入|
|OpAryGet |p1|p2|p3| |p3=p1[p2];|配列の値を変数へ代入|
--[4]ついでのおまけ。HL-8まででは加算と減算だけ「a=b+c;」みたいな形式を1内部命令にコンパイルする最適化をサポートしていたが、これをすべての二項演算子に拡大する。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdint.h> // intptr_tを使うため.
#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;
intptr_t var[MAX_TC + 1]; // 変数. (!)
AInt var[MAX_TC + 1]; // 変数. (!)
int getTc(String s, int len) // トークン番号を得るための関数.
{
int i;
for (i = 0; i < tcs; i++) { // 登録済みの中から探す.
if (len == tl[i] && strncmp(s, ts[i], len) == 0)
break;
}
if (i == tcs) {
if (tcs >= MAX_TC) {
printf("too many tokens\n");
exit(1);
}
strncpy(&tcBuf[tcb], s, len); // 見つからなかったので新規登録.
tcBuf[tcb + len] = 0; // 終端文字コード.
ts[i] = &tcBuf[tcb];
tl[i] = len;
tcb += len + 1;
tcs++;
var[i] = strtol(ts[i], 0, 0); // 定数だった場合に初期値を設定(定数ではないときは0になる).
+ if (ts[i][0] == 34) { // 先頭がダブルクォーテーション
+ char *p = malloc(len - 1);
+ var[i] = (intptr_t) p;
+ memcpy(p, ts[i] + 1, len - 2); // 手抜き実装(エスケープシーケンスを処理していない).
+ p[len - 2] = 0;
+ }
}
return i;
}
int getTc(String s, int len) → HL-8aと同じなので省略
///////////////////////////////////////////////////////////////////////////////
int isAlphabetOrNumber(unsigned char c) → HL-2と同じなので省略
int lexer(String s, int tc[]) // プログラムをトークンコード列に変換する.
{
int i = 0, j = 0, len; // i:今s[]のどこを読んでいるか、j:これまでに変換したトークン列の長さ.
for (;;) {
if (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r') { // スペース、タブ、改行.
i++;
continue;
}
if (s[i] == 0) // ファイル終端.
return j;
len = 0;
if (strchr("(){}[];,", s[i]) != 0) { // 1文字記号.
len = 1;
} else if (isAlphabetOrNumber(s[i])) { // 1文字目が英数字.
while (isAlphabetOrNumber(s[i + len]))
len++;
} else if (strchr("=+-*/!%&~|<>?:.#", s[i]) != 0) { // 1文字目が普通の記号.
while (strchr("=+-*/!%&~|<>?:.#", s[i + len]) != 0 && s[i + len] != 0)
len++;
+ } else if (s[i] == 34 || s[i] == 39) { // "文字列" or '文字'.
+ len = 1;
+ while (s[i + len] != s[i] && s[i + len] >= ' ')
+ len++;
+ if (s[i + len] == s[i])
+ len++;
} else {
printf("syntax error : %.10s\n", &s[i]);
exit(1);
}
tc[j] = getTc(&s[i], len);
i += len;
j++;
}
}
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 intptr_t *IntP; // こう書くと IntP は intptr_t * の代わりに使えるようになる. (!)
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 };
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;
int i = -1, e0 = 0, e1 = 0;
ppc1 = 0;
(中略)
for (;;) {
tmpFree(e0);
+ tmpFree(e1);
! if (i < 0 || e0 < 0 || e1 < 0) return -1; // ここまででエラーがあれば、処理を打ち切り.
if (epc >= epc1) break;
! e0 = e1 = 0;
if (tc[epc] == TcPlPlus) { // 後置インクリメント.
epc++;
e0 = i;
i = tmpAlloc();
putIc(OpCpy, &var[i], &var[e0], 0, 0);
putIc(OpAdd1, &var[e0], 0, 0, 0);
+ } 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);
+ } else if (phrCmp(71, "[!!**0]", epc)) {
+ e1 = i;
+ i = tmpAlloc();
+ e0 = expr(0);
+ putIc(OpAryGet, &var[e1], &var[e0], &var[i], 0);
+ epc = ppc1;
} else if (TcAster <= tc[epc] && tc[epc] <= TcPerce && priority >= 4) { // * / %
i = exprSub1(i, 3, tc[epc] - TcAster + OpMul); // 左結合なので4より1小さくする.
} 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( 9, "!!*0 = !!*1 + 1;", pc) && tc[wpc[0]] == tc[wpc[1]]) { // 高速化のために+1専用の命令を用意(せこくてすみません).
putIc(OpAdd1, &var[tc[wpc[0]]], 0, 0, 0);
! } else if (phrCmp( 2, "!!*0 = !!*1 !!*2 !!*3;", pc) && TcEEq <= tc[wpc[2]] && tc[wpc[2]] <= TcShr) { // 加算, 減算など.
! putIc(tc[wpc[2]] - TcEEq + OpCeq, &var[tc[wpc[0]]], &var[tc[wpc[1]]], &var[tc[wpc[3]]], 0);
} else if (phrCmp( 4, "print !!**0;", pc)) { // print.
e0 = expr(0);
putIc(OpPrint, &var[e0], 0, 0, 0);
! } else if (phrCmpPutIc( 4, "print !!**0;", pc, 0, 1, OpPrint, &e0)) { // print.
(中略)
} else if (phrCmp(19, "if ( !!**0 ) break;", pc) && lbd > 0) {
! } else if (phrCmpPutIc( 7, "time;", pc, 0, 0, OpTime, &e0)) {
(中略)
} else if (phrCmp(19, "if (!!**0) break;", pc) && lbd > 0) {
ifgoto(0, IfTrue, binf[lbd + ForBrk ]);
+ } else if (phrCmp(20, "prints !!**0;", pc)) { // prints.
+ e0 = expr(0);
+ putIc(OpPrints, &var[e0], 0, 0, 0);
+ } else if (phrCmp(21, "int !!*0[!!**2];", pc)) {
+ e2 = expr(2);
+ putIc(OpAryNew, &var[tc[wpc[0]]], &var[e2], 0, 0);
+ } else if (phrCmp(22, "int !!*0[!!**2] = {", pc)) {
+ e2 = expr(2);
+ putIc(OpAryNew, &var[tc[wpc[0]]], &var[e2], 0, 0);
+ j = 0;
+ for (i = ppc1; i < pc1; i++) { // コンマ以外のトークンを数える.
+ if (tc[i] == TcCrBrCls) break;
+ if (tc[i] != TcComma) {
+ j++;
+ }
+ }
+ if (i >= pc1) goto err;
+ intptr_t *ip = malloc(j * sizeof (intptr_t));
+ j = 0;
+ for (i = ppc1; tc[i] != TcCrBrCls; i++) {
+ if (tc[i] == TcCrBrCls) break;
+ if (tc[i] != TcComma) {
+ ip[j] = var[tc[i]];
+ j++;
+ }
+ }
+ putIc(OpAryInit, &var[tc[wpc[0]]], (IntP) ip, (IntP) j, 0);
+ ppc1 = i + 2; // } と ; の分.
! } else if (phrCmpPutIc(20, "prints !!**0;", pc, 0, 1, OpPrints, &e0)) { // prints.
} else if (phrCmp(21, "int !!*0[!!**2];", pc)) {
e2 = expr(2);
putIc(OpAryNew, &var[tc[wpc[0]]], &var[e2], 0, 0);
} 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);
(中略)
}
AWindow *win; // この行を追加.
void exec()
{
clock_t t0 = clock();
IntP *icp = ic;
! intptr_t i, *a;
! AInt i, j, *a, sx, sy;
+ AInt32 *p32;
+ char s[100];
for (;;) {
switch ((int) icp[0]) {
(中略)
+ case OpPrints:
+ printf("%s\n", (char *) *icp[1]);
case OpEnd:
+ if (win != 0)
+ aFlushAll(win);
return;
(中略:ここの中のintptr_tもAIntに置換すること)
+ 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 OpAryNew:
+ *icp[1] = (intptr_t) malloc(*icp[2] * sizeof (intptr_t));
+ memset((char *) *icp[1], 0, *icp[2] * sizeof (intptr_t));
+ case OpSetPix0:
+ aSetPix0(win, *icp[1], *icp[2], *icp[3]);
+ icp += 5;
+ continue;
+ case OpAryInit:
+ memcpy((char *) *icp[1], (char *) icp[2], ((int) icp[3]) * sizeof (intptr_t));
+ case OpM64s:
+ *icp[1] = (((AInt64) *icp[2]) * ((AInt64) *icp[3])) >> *icp[4];
+ icp += 5;
+ continue;
+ case OpArySet:
+ a = (intptr_t *) *icp[1];
+ i = *icp[2];
+ a[i] = *icp[3];
+ case OpRgb8:
+ *icp[1] = aRgb8(*icp[2], *icp[3], *icp[4]);
+ icp += 5;
+ continue;
+ case OpAryGet:
+ a = (intptr_t *) *icp[1];
+ i = *icp[2];
+ *icp[3] = a[i];
+ 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と同じなので省略
///////////////////////////////////////////////////////////////////////////////
int main(int argc, const char **argv) → HL-5と同じなので省略
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.
(中略)
}
}
-プログラムは635行になりました。49行から始まって、思えば遠くまで来たものです。
-この先にHL-9やHL-9aもありますが、それらはお遊びみたいなもので、言語そのものの強化ではないので、言語としてはHL-8aでひとまずの完成と言えるかもしれません。
-プログラムは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)の最初にある程度書いたので、この節では省略します。
** (2) phrCmpPutIc()について
-HL-9を作り始めた時は、以下のような記述の繰り返しになってしまいました。
} else if (phrCmp(23, "aSetPix0(!!***8, !!**0, !!**1, !!**2);", pc)) {
e0 = expr(0);
e1 = expr(1);
e2 = expr(2);
putIc(OpSetPix0, &var[e0], &var[e1], &var[e2], 0);
} else if (phrCmp(24, "aWait(!!**0);", pc)) {
e0 = expr(0);
putIc(OpWait, &var[e0], 0, 0, 0);
} else if (phrCmp(25, "aFillRect0(!!***8, !!**0, !!**1, !!**2, !!**3, !!**4);", pc)) {
e0 = expr(0);
e1 = expr(1);
e2 = expr(2);
e3 = expr(3);
e4 = expr(4);
putIc(OpFilRct0, &var[e0], &var[e1], &var[e2], &var[e3]);
putIc(OpPrm, &var[e4], 0, 0, 0);
-こんな記述を繰り返すのは面倒で行数も食います。それでこれを短く書けるようにするために、phrCmpPutIc()という関数を作りました。
int phrCmpPutIc(int pid, String phr, int pc, int *pi, int lenExpr, int op, int *err)
{
int e[9], i, i0 = 0;
if (phrCmp(pid, phr, pc)) { // もしphrCmp()の結果が1だったら(=一致したら).
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(); // もしpiが0でなければ、e[0]にtmpAlloc()の結果を入れる.
i0 = 1;
}
for (i = i0; i < lenExpr; i++) { // e0 = expr(0); ~ に相当.
e[i] = expr(i);
}
putIc(op, &var[e[0]], &var[e[1]], &var[e[2]], &var[e[3]]); // putIc()で内部コードを出力.
if (lenExpr >= 5) { // e[4]以降も使われていたら、OpPrmを使って残りのパラメータを内部コードに記述.
putIc(OpPrm, &var[e[4]], &var[e[5]], &var[e[6]], &var[e[7]]);
}
for (i = i0; i < lenExpr; i++) { // e1~が負ではないことを確認、負なら*errで教える(pi==0なら、e0~)
if (e[i] < 0) {
*err = -1;
}
tmpFree(e[i]); // e1~をtmpFree()する.
}
return 1;
}
return 0;
}
-これがあれば、先の例は、
} 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)) {
-のように、if文の条件成立節の中に何も書かなくていいほどシンプルになります。
-細かいことを書きます。
--文字列リテラルでは、エスケープシーケンスを処理していません。だから改行(\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;
-配列から値を読むだけなら、これで何の問題もなく十分に機能します。
** (3) aclライブラリについて
-普通、グラフィックスを使ったプログラムはOS依存があり、だからOSが変わったら再学習しなければいけません。でもこのライブラリはアプリがOSに依存しないで済む方法で作ってあるので、OSやCPUが違ってもプログラムは同じになります(コンパイル手順は変わります)。
-だからaclライブラリを使って作ったプログラムはそのまま他でも使えます。将来他のOSに乗り換えることになったとしても無駄になりません。
-このような性質を持つのは、なにもこのaclライブラリだけであるというわけではありませんが、その中でaclライブラリはかなり使いやすい部類であると私は思います。
-しかし実際には、 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;で配列の値が更新されるようになります。
-aclライブラリの関数は、たいていaで始まる名前が付けられています。
** (4) aclライブラリの入手方法
-Windowsの場合: (Windows専用版を使う方法と、SDL2.0対応版を使う方法があります。)
--[[aclib21]](Windows専用版をMinGW-5.1.6で使う例)
--[[aclib07]](SDL2.0対応版をMinGW-5.1.6で使う例)
-MacOSの場合:
--準備中(とりあえず今は[[aclib06]]でしのいでください)
-Linuxの場合:
--[[aclib17]]
-Androidの場合: (Android 7.0以降が必要です。スマートフォンでもタブレットでもOKです。)
--[[aclib08]] (SDL2.0対応版をTermux+clangで使う例)
-ラズベリーパイの場合:
--準備中(とりあえず今は[[aclib06]]でしのいでください)
-「はりぼてOS」の場合:
--準備中
-その他(SDL2.0とCの標準ライブラリを使った一般的なやり方):
--[[aclib06]]
** (5) なぜHL-9ではaOpenWin()がいつも0を返して、aSetPix0()やaFillRect0()ではwinパラメータが無視される仕様にしたのか?
-普通にwinのポインタを返す仕様にすることもできたのですが、HL-9で遊んでいると何かの拍子にwinに(=aOpenWin()の返値を入れておいた変数に)適当な値を入れて上書きしてしまうことがあり、こうなってしまうとwinに対する操作が何もできなくなってしまいます。
-それならば、いっそのことwinの値はHL-9が内部で管理しておくことにして、ユーザには本当の値を教えないようにしました。そして描画関数が呼ばれた場合は、ユーザが指定したwinの値は無視して、HL-9内部で管理された値を使うようにしています。
-なんとなく思いつきでやってみただけです。あまり深い意味はありません。
** (6)[追記]aclライブラリの不備?
-ひょっとすると、上記で公開済みのaclライブラリは、いくつかの関数が抜けているかもしれないので、ここに足りない関数を書いておきます。
--後日aclライブラリ自体をきちんと整理しないといけないなあ。
static AUInt32 aXorShift32_i = 2463534242U;
ASTATIC void aXorShift32_seed(AUInt32 s)
{
aXorShift32_i = 2463534242U ^ s;
}
ASTATIC AUInt32 aXorShift32()
{
AUInt32 i = aXorShift32_i;
i = i ^ (i << 13);
i = i ^ (i >> 17);
i = i ^ (i << 5);
aXorShift32_i = i;
return i;
}
ASTATIC AInt32 aGetPix(AWindow *w, AInt16 x, AInt16 y)
{
return w->buf[y * w->xsiz + x];
}
-さらにacl.cのmain()関数の中の、aMain();呼び出しの直前に以下の一行を追加してください。
aXorShift32_seed((unsigned int) time(0));
** 次回に続く
-次回: ''a21_txt01_9''
-次回: [[a21_txt01_9a]]
*こめんと欄
#comment