* a23_useSelfMade #7
-(by [[K]], 2023.03.19)
--親ページ: [[a23_useSelfMade]]
** 2023.03.19 Sun #1
-HL-9にはwhileがないです。+=とかもないです。これは不便だと感じてきたので直そうと思いました。
~
** 2023.03.20 Mon #1
-gccには関数内関数の機能があります。easy-Cの基本コンセプトは「mainの中だけ書く」なので、関数内関数が使えれば関数定義もできることになります。これはうれしい!
~
** 2023.03.22 Wed #1
-今日はeasy-Cでの開発の様子を紹介します。数学の公式にcomb(n, k)で、kを0~nまでの和を取ると必ず2^nになるというものがありますが、それを実際に計算してみて検証するというのをやってみます。
-''[1]''まずはパーミュテーションの計算をする関数を作りたいと思います。
int perm(int n, int k)
{
int i = 1, j;
for (j = 0; j < k; j++) { i *= n--; }
return i;
}
-これを適当なテキストファイルに入力します。ここではとりあえず20230322.cにしました。その上で、ec_inc0.cを開いて
// ここに追加関数へのincludeを自由に書く.
#include "20230322.c"
と書き足します。これで準備完了です(コンパイルとかしません)。さて、まず、うまく動くかどうかテストします。
HL9>!pr perm(4, 0)
1
HL9>!pr perm(4, 1)
4
HL9>!pr perm(4, 2)
12
HL9>
-(知らない人のために説明しておくと!はgccモードでの実行を意味していて、prというのはprintf("%d", ?)の略です。)
-とてもうまく計算できているようです。よしよし。
-''[2]''次はコンビネーションの計算です。先ほどの20230322.cに書き足します。
int comb(int n, int k) { return perm(n, k) / perm(k, k); }
-そしてまたワンライナーでテストです。
HL9>!pr comb(4, 2)
6
HL9>!pr comb(5, 3)
10
-これまた合っているようです。絶好調です。
-''[3]''ここまでできれば後は簡単です。
HL9>![n = 0:<=10] { int sum = 0; sum += comb(n, 0:<=n); printf("n=%d sum=%d pow=%d\n", n, sum, (int) pow(2, n)); }
n=0 sum=1 pow=1
n=1 sum=2 pow=2
n=2 sum=4 pow=4
n=3 sum=8 pow=8
n=4 sum=16 pow=16
n=5 sum=32 pow=32
n=6 sum=64 pow=64
n=7 sum=128 pow=128
n=8 sum=256 pow=256
n=9 sum=512 pow=512
n=10 sum=1024 pow=1024
-おお、ちゃんと検証できました!
-''[解説]''
-HL-9にはgcc実行モードというのがあって、それを使うと後付けで作ったC言語の関数があたかも最初からあった組み込み関数のように使えます。今回はそれを使っています。
-[3]では簡易ループ演算子を多用しています。[n = 0:<=10]はfor (n = 0; n <= 10; n++)と同じ意味です。sum += comb(n, 0:<=n);は[tmp = 0:<=n] { sum += comb(n, tmp); }と同じ意味です。
-easy-Cでは、i~nの変数はintで宣言済みなので、自分で宣言しなくても自由に使えます。
-この作り方で作っていくと、関数を一つ作るたびに「ちゃんと動くかな?」と手軽に確かめられるので、テストをまめにするようになりました。できあがったら、必要な関数を全部切り出して(コピー&ペーストするだけですが)、別のファイルにまとめて清書とします。・・・easy-Cがなかったころは、こまめにテストをしたほうがいいと分かっていても面倒で実践できていませんでした。それでデバッグになってから苦労していました。
-このやり方になってから、C言語がコンパイラだとあまり感じなくなって、使用感的にはインタプリタだなーって思います。楽しいです。
~
** 2023.03.22 Wed #2
-OSCが近いので明日までで開発を切りのいいところまで進めて、いったんリリースすることにします。
~
** 2023.03.23 Thu #1
-easyc003a.zipを公開しました。 → [[a23_ec001]]
~
-''[開発環境としてみたときのeasy-Cの特徴]''
-ワンライナーができる。
-別ファイルに関数を書いておくと、それを組み込み関数のように使える(参考: 2023.03.22 Wed #1)。
-プロンプトで$を使えばシェルコマンドが実行できる。
-インタープリタ実行モードもある。
-''[言語拡張としてみたときのeasy-Cの特徴]''
-prなどの便利命令が使える。
-簡易ループ演算子が使える。
-AXFncを使うと、関数の返値を複数で受け取れる。従来は&yや&mなどのようにポインタを渡していた。
- → 例: AXFnc_i3(y, m, d, =, fixDate(2023, 1, 100)); // 2023年1月100日を4月10日に直してくれる.
-''[プログラミング入門としてみたときのeasy-Cの特徴]''
-入門に最適なPLAY命令がある。
-入門者が使いそうな命令については、大文字でも入力できる。PRやPLAYやIFなど。
-ソースファイル作成は「NEW ファイル名」、テキストエディタ起動は「$$ファイル名」でできるようになっていて、ファイルエクスプローラ無しで操作できる。
-I~N(i~n)については、変数がintで宣言済みなので、宣言せずに使える。
-グラフィック描画ライブラリが使える(簡単なゲームが作れるように)。
** 2023.03.23 Thu #2
-fixDateの実装例
int getMonthDays(int y, int m)
{
static int days[12] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
if (m == 2 && y % 4 == 0) return 29; // 手抜きの閏年判定.
return days[m - 1];
}
#define fixDate(y, m, d) ACA_fixDate(aCA, y, m, d)
void ACA_fixDate(AComArg *aCA, int y, int m, int d)
{
while (d > getMonthDays(y, m)) {
d -= getMonthDays(y, m); m++;
if (m > 12) { m -= 12; y++; }
}
aCA->com->retValInt[0] = y;
aCA->com->retValInt[1] = m;
aCA->com->retValInt[2] = d;
}
HL9>!int y, d; AXFnc_i3(y, m, d, =, fixDate(2023,1,100)); printf("%d/%d/%d", y, m, d)
2023/4/10
~
** 2023.03.24 Fri #1
-OSCで何を紹介するかを考え中です。2023.03.23 Thu #1 に書いた内容に自己採点をしてみました。
|''[開発環境としてみたときのeasy-Cの特徴]''|RIGHT:小計+3.9点||
|#00 ワンライナーができる。|RIGHT:+1.0点||
|#01 別ファイルに関数を書いておくと、それを組み込み関数のように使える。|RIGHT:+0.9点|(参考: 2023.03.22 Wed #1)現状ではインタプリタモードで使えないのでその分を減点。|
|#02 プロンプトで$や$$を使えばシェルコマンドが実行できる。|RIGHT:+1.0点||
|#03 gcc実行モードのほかにインタープリタ実行モードがある。|RIGHT:+1.0点||
||||
|''[言語拡張としてみたときのeasy-Cの特徴]''|RIGHT:小計+2.2点||
|#04 prなどの便利命令が使える。|RIGHT:+1.0点||
|#05 簡易ループ演算子が使える。|RIGHT:+0.9点|現状の実装はまだちょっと手抜きがある。|
|#06 AXFncを使うと、関数の返値を複数で受け取れる。|RIGHT:+0.3点|インタプリタモードでも使えるようになれば+0.4にしてもいい。構文がもっとすっきりしたら+0.5にしてもいい。|
||||
|''[プログラミング入門としてみたときのeasy-Cの特徴]''|RIGHT:小計+4.5点||
|#07 入門に最適なPLAY命令がある。|RIGHT:+1.0点||
|#08 入門者が使いそうな命令については、大文字でも入力できる。PRやPLAYやIFなど。|RIGHT:+1.0点||
|#09 「NEW ファイル名」「$$ファイル名」によって、ファイルエクスプローラ無しで操作できる。|RIGHT:+1.0点|すごく簡単な機能なのに効果は大きい。|
|#10 I~N(i~n)については、変数がintで宣言済みなので、宣言せずに使える。|RIGHT:+0.5点||
|#11 グラフィック描画ライブラリが使える(簡単なゲームが作れるように)。|RIGHT:+1.0点||
-こうしてみると、言語拡張としてはまだまだ弱いことがよくわかります。私自身も直感的に感じていましたが、こうして可視化すると納得です。
-それで、まあ春のOSCには間に合わないものの、この先1~2か月で何とかできそうなことを考えてみました。いずれも言語拡張系です。
|''[言語拡張としてみたときのeasy-Cの特徴]''|RIGHT:小計+2.1点|(上の#04~#06と合わせると、合計+4.3点になる)|
|#12 インタプリタモードの時だけ、演算子の優先順位を変更できる。|RIGHT:+0.3点|(gccモードでもできるようにするには結構な改造が必要。でもそれができたら+0.5点にしてもいい。)|
|#13 gccモードでの、#longdef, #endlongdef, #longuse|RIGHT:+0.8点|(プリプロセッサ拡張、昨年別の開発のために作って便利だった)|
|#14 Uniform Function Call Syntax に対応。|RIGHT:+1.0点|これはすぐにはできなさそう。でもいつかやりたい・・・。|
** 2023.03.27 Mon #1
-小学生2名が仲良く遊べるような、協力型のゲームを一つ作りました。
-へびゲーム系のもので、1マス進むごとに+1点ですが、協力型ゲームになるように両方が生きて進んでいるときはさらにボーナスの+1点が入ります。点数はひとつだけで、プレイヤーごとには集計しません。反射神経に自信がなくても楽しめるように「止まる」という操作があります。
-hb2.c[62行]
int spd = 128, hi = 0; AWin *w = aOpenWin(800, 600, "HB2");
int dir[2], bom[2], x[2], y[2], col[2], scr, dxy[14] = { 8,0, 0,-8, -8,0, 0,8, 0,0, 0,0, 0,0 };
start:
aFillRect(w, 800, 584, 0, 16, 0xffffff); aFillRect(w, 784, 568, 8, 24, 0x000000);
[i=0:<2] { dir[i] = 4; bom[i] = 0; y[i] = 304; } x[0] = 264; x[1] = 528;
col[0] = 0x00ff00; col[1] = 0x0000ff; scr = 0;
[9] { aFillOval(w, 8, 8, aRnd(96) * 8 + 16, aRnd(68) * 8 + 32, 0xffff00); }
while (dir[0] != 6 || dir[1] != 6) {
aGrPrintf(w, 16, 0, 0xffffff, 0, "SCORE:%05d HIGH:%05d BOM1:%d BOM2:%d", scr, hi, bom[0], bom[1]);
[i=0:<2] { if (dir[i] < 6) { aFillOval(w, 8, 8, x[i], y[i], col[i]); }}
while ((k = aInkey(w, 1)) != 0) {
char *p = "swazxq:@;/\\p", *q;
if ('A' <= k && k <= 'Z'){ k += 'a' - 'A'; }
q = strchr(p, k);
if (q == 0) continue;
if (q - p < 6) {
if (dir[0] < 6) { dir[0] = q - p; }
} else {
if (dir[1] < 6) { dir[1] = q - p - 6; }
}
}
[i=0:<2] {
x[i] = x[i] + dxy[dir[i] * 2 + 0];
y[i] = y[i] + dxy[dir[i] * 2 + 1];
j = aGetPix(w, x[i] + 4, y[i] + 4);
if (j == 0xffff00) { j = 0; bom[i] = bom[i] + 1; }
if (dir[i] < 4 && j != 0) {
x[i] = x[i] - dxy[dir[i] * 2 + 0];
y[i] = y[i] - dxy[dir[i] * 2 + 1];
dir[i] = 6;
}
if (dir[i] == 6) { aFillOval(w, 8, 8, x[i], y[i], 0xff0000); }
if (dir[i] == 5) {
if (bom[i] > 0) {
bom[i] = bom[i] - 1; scr += 100;
for (j = -1; j <= 0; j++) {
for (l = 24; l < 592; l += 8) {
n = (l - y[i]) * (l - y[i]);
for (k = 8; k < 792; k += 8) {
m = (k - x[i]) * (k - x[i]);
if (n + m < 6400) { aFillRect(w, 8, 8, k, l, j & 0xffffff); }
}
}
if (j < 0) { aWait(256); }
}
}
dir[i] = 4;
}
if (dir[i] < 4) { scr++; }
}
aWait(spd);
if (dir[0] < 4 && dir[1] < 4) { scr++; }
if (AWin_isClose(w)) goto end;
if (dir[0] == 6 && dir[1] == 6) break;
}
if (hi < scr) { hi = scr; }
for (;;) {
aWait(128);
k = aInkey(w, 1);
if (k == AWinKey_Esc || AWin_isClose(w)) goto end;
if (k == AWinKey_Enter) goto start;
}
end:
https://essen.osask.jp/files/pic20230327a.png
||色|上|左|右|下|止|爆|
|1P|緑|W|A|S|Z|X|Q|
|2P|青|@|;|:|/|\|P|
-黄色を取るとボムが増えます。この黄色があるおかげで、とりあえずそれを取ろうという気持ちになるので、スタート時の「どうしたらいいのかわからない」感がだいぶ減りました。
** 2023.03.28 Tue #1
-オープンソースカンファレンスのためのチラシを作成・・・。
--https://essen.osask.jp/files/dc20230401_0a.pdf
** 2023.04.03 Mon #1
-03/29(水) サイボウズ・ラボユースの成果発表会
-03/30(木) OSCの準備(デモ環境の構築)
-03/31(金) サイボウズ・ラボユースの懇親会
-04/01(土) OSC東京春
-04/02(日) OSCの懇親会の代わりのお花見ジビエBBQ
-といろいろ忙しくしていたら更新をさぼってしまいました。今日から復活です。・・・まずは体力を戻さなければ・・・。
~
** 2023.04.04 Tue #1 [easy-C] [casm]
-まずは演算子の優先順位変更のことを考えつつ、「Cコンパイラをうまく使った、CPUに依存しないアセンブラ」が気になるので、それを考えています。
** 2023.04.05 Wed #1 [casm]
-また悪い癖が出て拡張的な仕様にしようとして進まなくなる・・・。うまく考えがまとまらないときは、それは自分のスキルが足りない時なのだから、考えても時間の無駄。だから適当にごまかし実装にしておいて後で考え直すほうがいい。・・・よし、そうします!
** 2023.04.05 Wed #2 [casm]
-迷っているところを妥協したらすぐにできました。
-casmというアセンブラです。
-t0001.c:
LdImm32(R00, 32); // MOV EAX,32;
Label(2); // L_2:
ApiPutCharReg32(R00); // PUSH EAX; CALL api_putchar; ADD ESP,4; // api_putcharはEAXも含めて何も破壊しない.
AddShtImm32(R00, 1); // ADD EAX,1;
CmpJmpNotEquImm32(R00, 127, 2); // CMP EAX,127; JNE L_2;
End();
-これをアセンブルして実行すると、
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
がでます。
** 2023.04.06 Thu #1 [casm]
-[Q] casmってなんですか?
-[A] 基本的にはアセンブラです。
-[Q] 既存のアセンブラと比較して何が違いますか?
-[A] たとえばx86の場合、レジスタ名はEAX, ECX, EDX, EBX,...などとなっています。他のCPUではまた違う名前になります。でもレジスタ名なんて本質じゃないと思うのです。だからこれらをR00, R01, R02, R03,...に統一します。またレジスタに定数を代入する命令もCPUによってまちまちですが、これも統一します(LdImm)。このように命令名を共通化して、移植可能にした(というか移植作業を不要にした)アセンブラです。
-[Q] それは一見便利そうに思いますが、たとえばx86のストリング命令のように特定のCPUしか持っていないような命令の互換性はどうなりますか。
-[A] そういう命令はcasmでは直接はサポートせず、api経由で利用します。移植性を優先するので、特定のCPUだけが持っている命令はサポートしません。
-[Q] レジスタ数はどうなりますか?
-[A] 整数レジスタが64本、ポインタレジスタが64本、浮動小数点レジスタが64本という想定です。
-[Q] え、それはx64ですらそんなにたくさんのレジスタはないですよ。
-[A] そうです。実レジスタを超えた分はメモリを使って代用します。この方針にしないと実レジスタの最も少ないCPUに合わせて仕様を決めることになってしまい、それはあまりにも性能で不利になるだろうと思いました。それで実レジスタの個数にかかわりなく、64本を想定してよい、足りない分はエミュレーション、ということにしました。
-[Q] だんだんわかってきました。つまりcasmの1命令がCPUの1命令に対応するとは限らないということですね。
-[A] 全くその通りです。だから純粋なアセンブラではなく、アセンブラ風の低級言語だというほうが合っているかもしれません。
-[Q] そういうことであれば、C言語でもいいのではないかと思いました。ほら、C言語は現代のアセンブラだと言うこともありますよね?
-[A] もちろんそれでもいいのですが、しかしC言語の処理系は作るのが大変じゃないですか?・・・たとえば自分でCPUを自作したとします。その自作CPUのためのC言語をいきなり作れますか?casmのコンパイラ(アセンブラ?)を書くほうがずっと簡単じゃないですか?
-[A] casmではすべての命令がC言語のマクロみたいな方法で記述されています。だから簡単な置換でよければC言語のプリプロセッサだけでできてしまいます。それを利用すれば、casmのアセンブラを作るのは少し楽になるかもしれません。
-[Q] 他に特徴はありますか?
-[A] casm32.cというファイルが提供されていて、これをインクルードファイルに指定してt0001.cをCコンパイラでコンパイルすると、なんとコンパイルが最後まで通って、実行ファイルができます。これはcasmのそれぞれの命令がC言語の命令に置き換えられているためです。
-[A] つまりCコンパイラさえあれば、casmの処理系を準備しなくてもcasmのソースコードから実行ファイルが作れるのです。
-[Q] そもそもアセンブラを使いたくなる理由として、速度が欲しいというのがあると思うのですが、これで十分な速度が出るのでしょうか?
-[A] まず、普通のアセンブラを全力で使った場合には勝てないだろうと思っています。高級言語のインタプリタよりは確実に速いと思いますが、コンパイラ型の言語には負けるかもしれません。
-[A] じゃあ何のためにアセンブラなんか使うのか。それは結局移植性のためです。(4/9へ続く)
** 2023.04.07 Fri #1 [casm]
-t0002.c:
LdImm32(R00, 256); // MOV EAX,256;
ApiWindowReg32(R00, R00); // PUSH EAX; PUSH EAX; CALL ApiWindow; ADD ESP,8;
XorSht32(R03, R03); // XOR EBX,EBX;
XorSht32(R02, R02); // XOR ECX,ECX;
Label(2); // L_2:
XorSht32(R01, R01); // XOR EDX,EDX;
Label(3); // L_3:
ApiSetPixReg32(P00, R01, R02, R03); // PUSH EBX; PUSH EDX; PUSH ECX; PUSH ESI; CALL ApiSetPix; ADD ESP,16;
AddShtImm32(R01, 1); // ADD ECX,1;
AddSht32(R03, R00); // ADD EBX,EAX;
CmpJmpNotEqu32(R01, R00, 3); // CMP ECX,EAX; JNE L_3;
AddShtImm32(R02, 1); // ADD EDX,2
CmpJmpNotEqu32(R02, R00, 2); // CMP EDX,EAX; JNE L_2;
ApiWaitForExit32(); // CALL ApiWaitForExit;
End();
https://essen.osask.jp/files/pic20230407a.png
-補足
--XorやAddなどの演算命令についているShtはshortの略。ショート形式。つまりは二項型。これに対してLngもあって、それは三項型。
--Immがついているものは最後の引数がimm。
** 2023.04.09 Sun #1 [casm]
-結局casmで書いてもアセンブラで書くみたいには高速化されなくて、じゃあ何のためにこれを使うのかということになりました。高速化されないとしても私はcasmに魅力を感じていて、それをどうにかして説明したいのです。
-私はcasmを自作のLLVMモドキだと思っているのです。LLVMの場合、コンパイラはLLVMのフォーマットで出力すれば、あとは最適化も互換性も全部面倒を見てくれます。これは私から見ても非常にかっこいいと思いますが、しかし一方で、現状のLLVMは多機能すぎて大きすぎます。ここまでのものを自作したいかというとさすがにそうは思いません。LLVMみたいな高度な最適化をあきらめれば、簡単な実装でも移植性は提供できると思うのです。それでcasmをやりたいわけです。
-ここまで書いて分かったのは、casmがLLVMの劣化版だとするなら、レジスタ数に上限があるのは便利ではないです。だから上限を撤廃します。
-うーん、次の一手としては、HL-9がcasmを出力するとか??
** 2023.04.10 Mon #1 [casm]
-x64が出たとき、関数呼び出しの方法が変わって、一部レジスタ渡しになりました。でもたとえばx86に似せるのなら、RAX, RCX, RDXのみを破壊されうるレジスタとして残りをすべて保存させる道もあったはずです。・・・他の方法をどれほど試したのか、つまりどうしてこうなったのか、私はいまいちよくわかりません。
-でも自分でcasmみたいなものを作ればいろいろ実験ができる気がします。
-第一世代OSASK方式:
--引数へのポインタはEBXで渡していました。たいていの場合スタックにPUSHした後、EBX=ESP;として、それからCALLしていました。このやり方のいいところは、定数パラメータしかないような場合なら、EBXにそのポインタを入れるだけでスタック操作なしに関数呼び出しができることでした。
** 2023.04.10 Mon #2 [casm]
-C言語にはintptr_tという型があります(同様のものがacl1ライブラリにもあります。AIntです)。これはレジスタが汎用レジスタになっている場合は、まあ素直で妥当ではあります(整数レジスタとポインタレジスタに分かれている場合は、素直ではないし妥当でもないです)。
-そもそもポインタを整数のフィールドに格納しておきたいときっていうのは(もしくはその逆は)、要するにunionの代用をしているのだと思います。それならちゃんとunionとして記述されるべきではないかと今は思います。
-理想ではそうでも、実際はポインタからintに変換しているようなケースはまあまああるので、これをないがしろにすると移植の際に苦労するかもしれません。・・・うーん、でも、これを機会にそういうコードは直されるべきかもしれません。
** 2023.04.11 Tue #1 [casm] [easy-C]
-よし、とりあえずHL-9でcasmの出力ができるようになりました。
HL9>print 1+2
3
HL9>casm 1
HL9>print 1+2
LdImm32(R00, 1);
AddLngImm32(R01, R00, 2);
ApiPrintReg32(R01);
End();
HL9>print 1+2+3
LdImm32(R00, 1);
AddLngImm32(R01, R00, 2);
AddLngImm32(R02, R01, 3);
ApiPrintReg32(R02);
End();
HL9>
//関数呼び出しインタフェース
// *=など、ACalFnc
// persistent-C
//一覧を得る関数があって、後は自分でフィルタ・ソートする。
//kvs, 言語は型を知るべき, 言語はオブジェクトを知るべき
//winのクローズ
* こめんと欄
-掲示板をご利用ください。→[[a23_bbs]]