* a23_useSelfMade #1
-(by [[K]], 2023.01.10)
--親ページ: [[a23_useSelfMade]]
** 2023.01.10 Tue #1
-小学生にC言語を教えようと思ったときに、#includeとかmainとか、なんか毎回そんなことを書かなきゃいけないなんて、ハードルが高い言語だなって改めて思いました。・・・RubyやPythonなら、そんなことで苦労したりはしません。
-「だったらC言語なんか教えないでRubyやPythonを教えればいいだろ」ってなるわけですが、まあそこはいったん保留にして、ハードルの低い手軽なC言語があったらどうかな?と私は考えました。・・・そんなことを思いついてしまったのが運の尽きで、思いついたからには試してみたくなってしまいました。
--それに、そんな本質的じゃない理由(最初の導入ハードルがちょっと高い)だけでC言語を見捨てるなんて、それはあんまりだと思うのです。それくらいのことで言語を切り替えていたら、今後何度言語を切り替えていかなければいけないのか、先が思いやられます。・・・というのは今思いついたいいわけです(笑)。
-仕組みは笑ってしまうくらいに単純です。指定された入力ファイルの前に #include <...>をたくさんつけて(標準ライブラリ全部)、さらに int main(int argc, const char **argv) { もつけて、あと入力ファイルの後には return 0; } もつけて、それを tmp.c として出力し、gccでコンパイルして tmp.exe を生成し、エラーがなければそのまま tmp.exe を実行するというただそれだけのものです。
-私はこれをひとまず「easy-C」と呼ぶことにしました。「お手軽なC」くらいの意味です。
-この仕組みがあれば、
puts("hello, world");
だけで実行できます(コマンドラインから「prompt>ecrun hello.c」とするだけで)。
-いや、そんなのこの仕組みからすれば当たり前なのですが、私は結構衝撃を受けました。なんかすごくC言語っぽくない!!・・・エラーなく動いている場合、C言語がコンパイラであることを忘れそうになります。
~
** 2023.01.10 Tue #2
-easy-Cでプログラムをいくつか書いていると、そもそも1行程度のプログラムの場合、ファイルに保存するのすら面倒になってきます。そう、ワンライナーがやりたいのです(笑)。・・・私って本当に怠け者ですね・・・。
-ということで、1ecというコマンドを作って、
prompt>1ec "puts('hello, world')"
ってできるようにしました。1ecというコンソールアプリは、第一引数の内容に対してシングルクォーテーションをダブルクォーテーションに置換して、末尾にセミコロンを付与して、その内容の前後に上記のコードを足してtmp.cを作って・・・以下同文のやつです(「末尾のセミコロン付与」は、少しでもキータイプ量を減らそうという、怠け者の発想です)。
-たとえばこんなことができます。
prompt>1ec "int i, j; for (j = 1; j <= 9; j++) { for (i = 1; i <= 9; i++) printf('%3d', i * j); printf('\n'); }"
1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81
-このC言語っぽくない感じが伝わるでしょうか?・・・唯一の難点は、ワンライナーで入力してEnterを押してから、実行が始まるまでに数秒の間があることです。そこだけが「ん?」と感じます。
-C言語のワンライナーは本当に便利で、「あれ?gccでは long double って何バイトになるんだっけ?」みたいに思いついたときに、すぐに試せます。
prompt>1ec "printf('%d', sizeof (long double))"
12
--ああ確認してよかった。10バイトかと思ったらそうじゃなかった!
~
** 2023.01.11 Wed #1
-小学生にprintfを教えようと思うと、またしてもハードルを感じます。 printf("%d", i); ってちょっとあんまりじゃないですか?ただ変数の値を表示させたいだけなんですよ。それ以上の凝ったことがしたいわけじゃないのです。Rubyを見てくださいよ、「p i」ですよ。これ以上シンプルにできるか?って言われた気分になるほどシンプルです。すばらしい。
-ということで、easy-Cでも似たようなことを試みます。と言っても大したことはなくて、「#define pr(i) printf("%d", i)」を付加しただけです。でもこれで、
prompt>1ec "pr(1+2+3)"
ってやるだけで、答えが出るようになりました。今まではprintfって6文字も書かされていましたし、'%d'も面倒でした。
-Rubyのようにpだけにすることも考えたのですが、それだと変数名でpが使えなくなるかもしれないと思い、私は結構な頻度でpやqという変数を使うので、ここは妥協して2文字のprにしました。
-私が小学生にマスターさせたいC言語の命令として、printfとgotoとifの3つがあります。今printfはprになりました。ifは最初から2文字です。それならgotoだって2文字にしたいです(笑)。そんな実に下らない理由で、goというマクロも作りました。
-ということで、他に作ったマクロも含めて全部でこんな感じにしました。
#define pr(i) printf("%d", i)
#define prf(f) printf("%f", f)
#define prs(s) printf("%s", s)
#define prsp() printf(" ")
#define prlf() printf("\n")
#define go(l) goto l
-でもまあとりあえず、私が頻繁に使うのはprとprfだけです。prfがあれば関数電卓とかいらないかもしれないです。
prompt>1ec "prf(pow(27,1.0/3))"
3.000000
~
** 2023.01.11 Wed #2
-私の教えた経験からすると、小学生はカッコが嫌いです。カッコなんてめんどくさいです。私だってカッコはあまり好きじゃないです。書かないで済むなら書きたくないです。というかカッコが多くても気にならない人は、C言語ではなくてLISPをやったほうがいいかもしれません(LISPはカッコが多いことを許容する代わりに、シンプルで柔軟な機能を手に入れた言語だと私は思います)。
-ということで、prとprfとprsとgoなどに限って、カッコを省略できるようにしてみました。といっても難しい判定アルゴリズムは実装したくないので、命令の直後に(が無かったら(を追加し、文末に)を追加するというただそれだけのものです。
-それとついでに、小学生は小文字のアルファベットが読めないので、PRとかGOなどと大文字で書いてもいいことにしました。大文字ならキーボードの刻印と同じなので、小学生でも探せます。
-こうして私は
prompt>1ec "pr 1*2*3*4"
24
prompt>1ec "prf sqrt(256)"
16.000000
ができるようになりました。カッコが省略できるようになっただけなのですが、これは小学生だけではなく私にも便利です!
-私としては、小学生に対して、最初のころはPRやGOやIFを使って簡単なプログラムを書けるようにさせようと思いますが、だいたいできるようになった後は「本当はC言語では命令は小文字で書かなきゃいけないし、パラメータにはカッコをつけなきゃいけないんだよ」と教えて、pr(...)とかを使うようにさせて、最後にはprintfとかにさせようと思います。だからPRやGOなどはやがては卒業する構文なので、複雑なことができないのは全く問題ないです。複雑なことをしたくなったら、こんな簡易構文は卒業すればいいわけです。
~
** 2023.01.12 Thu #1
-easy-Cによるワンライナーをやっていると、コマンド全体をダブルクォーテーションでくくるのが面倒になってきました。でもくくらないと、 for (i = 0; i < 10; i++) と書くだけでも、不等号の部分を標準入力へのリダイレクト指定だとみなされて、それ以降をコマンドライン引数として受け取れなくなります。うーん。
-そこで発想を転換して、gets()するアプリを書いて、もし最初の6文字が"shell "だったらそれ以降の文字列をsystem()関数で実行し、そうでなければ、easy-Cでワンライナーする、という処理をさせてみました。プロンプトは「1ec>」にしています。
1ec>pr 12*34
408
1ec>prs "hello\n"
hello
1ec>int c; for (c = 0; c < 10; c++) pr c
0123456789
1ec>
-すごくうまくいっています!普通のC言語を使うときは、
1ec>shell gcc -O3 -Wl,-s -o test.exe test.c
1ec>shell test.exe
などとやっています。すごく便利で楽しいです。この楽しさが伝わるでしょうか。
-今、唯一の悩みは、easy-Cでミスって無限ループを実行してしまい、やむなくCtrl-Cを押すわけですが、そうするとこのeasy-Cシェルも終了することになってしまうので、また立ち上げ直さなければいけないということです(まあ、そんなの1ec.exeを起動するだけなので簡単なのですが、それでもなんだかくやしいのです)。
-[追記] https://learn.microsoft.com/ja-jp/windows/console/setconsolectrlhandler に書いてある方法を使えば、Ctrl-Cがアプリから拾えるかも!
~
** 2023.01.12 Thu #2
-変数iや変数jはとてもよく使うのですが、そのたびにint i, j;とかやるのってちょっと面倒です。HLシリーズとかHLXとかでは、変数宣言は不要でした。しかしC言語で変数宣言不要にするのはかなり難しそうな気がしたので、i, j, k, l, m, nの6個だけは「あらかじめintで宣言しておく」という仕様にしてみました。
-これって小学生に教える時に有害かなと心配したのですが、むしろ雑な整数変数としてi~nを積極的に使う習慣ができるかもしれないから、それはそれでいい気がしました。これがないと小学生はたぶんa, b, cあたりを積極的に使ってしまうはずです。
-九九の表もこんなに短く書けるようになっています。
1ec>for (j = 1; j <= 9; j++) { for (i = 1; i <= 9; i++) { printf("%3d", i * j); } prlf; }
1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81
~
** 2023.01.13 Fri #1
-ワンライナーではなく普通に何行か書いたプログラムについては ecrun というバッチファイルで実行していますが、同じものを連続して何度も実行するとき、そのたびにgccが走るのはいかにも無駄です。・・・ということで、コンパイルに成功した場合はそのソースファイルとコンパイル結果をどこかに保存しておいて、次からはコンパイル前にファイル内容を比較して、一致したらコンパイルはしないで保存しておいたコンパイル結果をそのまま使うようにしてみました。
-なかなか快適です!・・・これで実行ファイルを手元に保存しておく必要をあまり感じなくなりました。私が大きなプログラムをあまり書かないせいもあると思います。今の私にとってはC言語はかなりインタプリタみたいな使用感です。
~
** 2023.01.13 Fri #2
-easy-Cを使っていると、とにかくディスクの書き換えが何度も発生するので(上記のコンパイル結果のキャッシュが効いているときは全く書き換えずに済みますが)、SSDの書き換え寿命が心配になってきます。私はそんなに高価なSSDを使っているわけではないので、寿命を気にせずに使っていると、すぐに寿命が来そうで怖いです。
-ということで、なんかいい感じのRAMディスクはないかなと探してみました。
-私はImDiskにしてみました。
--https://syumi3.com/sonota/html_pasokon/imdisk-toolkit.html
--https://forest.watch.impress.co.jp/library/software/imdiskvdiskr/
--https://sourceforge.net/projects/imdisk-toolkit/
-他にも高速そうなRAMディスクがいくつかありましたが、やっぱりオープンソースなのは安心感があっていいなと思いました。
-私は64MBくらいのRAMディスクを作って、そこで1ecをずっと起動して使っています。再起動すると消えるので、消えては困るものは別のところに置いています(ecrunしたくなったら、shell copy ...でRAMディスクにコピーしてくる)。
-RAMディスクにしたことで速くなったか?・・・というと、もともとSSDだったせいで速くて差を体感できないのですが、寿命の心配をしなくてよくなったというのは気分的には非常にいいです。これで遠慮なく何度でもワンライナーできます。
~
** 2023.01.14 Sat #1
-直近の予定。今日と明日は開発お休み。月曜日にはもちろん再開。
-ここで作っているものは小さいものばかりだけど、でもちりも積もれば山となるだから、月末には一度アーカイブを作ってダウンロードできるようにしたいです。
~
** 2023.01.16 Mon #1
-easy-Cはacl1ライブラリを使うことで、グラフィックが使えるようになりました。acl1ライブラリは、何年か前に作っていたaclライブラリの全面改訂版です(ちなみにまだ完成してないです)。
-まあまずは定番のこれをやりました。
1ec>AWin *w = aOpenWin(256, 256, "graphics"); for (j = 0; j < 256; j++) { for (i = 0; i < 256; i++) { aSetPix(w, i, j, aRgb8(j, i, 0)); }}
http://k.osask.jp/files/pic20230116a.png
https://essen.osask.jp/files/pic20230116a.png
-C言語のワンライナーでこれができる日が来るとは!(一人で感激)
~
-次はこれです。
1ec>AWin *w = aOpenWin(256, 256, "graphics"); for (j = 0; j < 8; j++) { for (i = 0; i < 8; i++) { aFillRect(w, 32, 32, i * 32, j * 32, ((i ^ j) & 1) * 0xffff00); }}
http://k.osask.jp/files/pic20230116b.png
-うんうん、うまく動いています。こういうのを思いついたときに、ワンライナーで試せるのはすごくいいと思います。
-ちなみにこうやって試しながら、acl1ライブラリや1ecにバグを見つけては直しています(笑)。
~
-次はaDrawLineを試しました。
1ec>AWin *w = aOpenWin(256, 256, "graphics"); aSetMode(w, AWinMode_Xor); for (i = 0; i < 256; i++) { aDrawLine(w, 0, 0, i, 255, 0x00ffff); aDrawLine(w, i, 0, 255, 255, 0x00ffff); }
http://k.osask.jp/files/pic20230116c.png
https://essen.osask.jp/files/pic20230116c.png
-よしよし。
~
-次はaFillOvalを試しました(銀色の球に見える?)
1ec>AWin *w = aOpenWin(256, 256, "graphics"); for (i = 24; i < 128; i++) { j = 127 - i; aFillOvalCent(w, 128, 128, j, j, 0x020202 * i); }
http://k.osask.jp/files/pic20230116d.png
https://essen.osask.jp/files/pic20230116d.png
-これもOK。
~
-Wikipediaによると、日本の国旗は「旗の形は縦が横の3分の2の長方形。日章の直径は縦の5分の3で中心は旗の中心。地色は白色、日章は紅色」だそうです。そのように書いてみました。
1ec>AWin *w = aOpenWin(384, 256, "hinomaru"); aFillRect(w, 384, 256, 0, 0, 0xf8f8f8); aFillOvalCent(w, 192, 128, 77, 77, 0xff0000)
http://k.osask.jp/files/pic20230116e.png
https://essen.osask.jp/files/pic20230116e.png
-おおなるほど、これは確かにそれらしい感じに見える!
~
-rynaさんから教えてもらった、XORで書けるすてきな模様。
1ec>AWin *w = aOpenWin(256, 256, "xor"); for (j = 0; j < 256; j++) { for (i = 0; i < 256; i++) { aSetPix(w, i, j, 0x010101 * (i ^ j)); }}
http://k.osask.jp/files/pic20230116f.png
https://essen.osask.jp/files/pic20230116f.png
-きれいだなー。
~
-ワンライナーがあると、ちょっとこれを試してみようっていうのがすぐできる。当たり前だけど、試せば試すほどデバッグは進む(はかどる)。これはいいなー。
--だからなんか作りかけのライブラリか何かをインクルードした状態にさせるコマンドを作って、その状態でワンライナーできるようにしたら、すごく便利かもしれない!
~
** 2023.01.17 Tue #1
-[[坂井弘亮さん(kozosさん)>https://kozos.jp/]]が最近開発されている言語に、NLLというのがあります。URLはこちらです。 → https://kozos.jp/nll/
-そこに「サンプル集2」というのがあって、「立体の波(動きます) g_3dwave.hob」というのがあります。画面写真を見ると、おおこれはなかなかきれいです。
-ということで、これをeasy-Cでもやってみたいと思いました。
-まずオリジナルのg_3dwave.hobはこんな感じです。
1 GSCREEN(G_AUTOMODE); GSETOFFSET(320,240); GSETSCALE(4,2)
2 R=0.0
3 .MAINLOOP; GCLEAR(G_BLACK); R -= 0.1
4 LOOP X0, 40, -20; LOOP Y0, 40, -20; X1 = X0 + 1; Y1 = Y0 + 1
5 (D0, D1, D2) = SQRT(X0*X0+Y0*Y0, X1*X1+Y0*Y0, X0*X0+Y1*Y1)
6 (Z0, Z1, Z2) = SIN(R+D0, R+D1, R+D2) * (50/(D0+5),50/(D1+5),50/(D2+5))
7 GLINE(X0*2-Y0*2, X0*2+Y0*2+Z0, X1*2-Y0*2, X1*2+Y0*2+Z1, G_CYAN)
8 GLINE(X0*2-Y0*2, X0*2+Y0*2+Z0, X0*2-Y1*2, X0*2+Y1*2+Z2, G_CYAN)
9 LOOPEND Y0; LOOPEND X0
10 GFLUSH(); WAIT 1; GOTO MAINLOOP
-これをeasy-Cに書き直したものが以下になります(16行になってしまったのが、ちょっと負けている感じはしますが・・・)。
AWin *w = aOpenWin(640, 480, "3dwave")
int r, x, y, gx[42][42], gy[42][42]
for (r = 0; w->phase != 2; r--) { // ウィンドウが閉じられるまでループ.
aFillRect(w, 640, 480, 0, 0, 0x000000)
for (y = -20; y <= 21; y++)
for (x = -20; x <= 21; x++) {
float d = sqrt(x * x + y * y), z = sin(r * 0.1 + d) * 50 / (d + 5)
int x0 = x + 19, y0 = y + 19, x1 = x + 20, y1 = y + 20
gx[y1][x1] = (x * 2 - y * 2 + z * 0) * 4 + 320
gy[y1][x1] = (x * 2 + y * 2 + z * 1) * 2 + 240
if (x0 < 0 || y0 < 0) continue;
aDrawLine(w, gx[y0][x0], gy[y0][x0], gx[y0][x1], gy[y0][x1], 0x00ffff)
aDrawLine(w, gx[y0][x0], gy[y0][x0], gx[y1][x0], gy[y1][x0], 0x00ffff)
}
aWait(8)
}
http://k.osask.jp/files/pic20230117a.png
https://essen.osask.jp/files/pic20230117a.png
-おお、ちゃんと波打っているー。実行するとちゃんと動きます。ちなみに実行は、
1ec>run 3dwave.c
とします。
~
** 2023.01.17 Tue #2
-今まで、ecrunコマンドはWindowsのバッチファイルで構成していました。なぜそうしたのかというと、その時はそれが手軽に思えたのです。しかしこれは失敗でした。なぜなら、Ctrl-Cで止めたときに「バッチ ジョブを終了しますか (Y/N)?」という表示が出るためです。ここでyを入力すればいいだけだろうと思っていたのですが、system関数でバッチファイルを起動している場合(1ec.exeからrunコマンドを実行するとこの状況になっていました)、なんか中で処理が混乱しているらしく、yを一度入れただけではうまくいかず、何度もやらなければいけませんでした。
-ということでバッチファイルをやめて、C言語でecrunを書き直しました。そうしたら「Ctrl-C」しても「バッチ ジョブを終了しますか (Y/N)?」は出なくなりました。まあバッチじゃないので当然なんですけどね。すごく便利になりました!
-しかも1ec.exeのほうで、「runコマンド実行中やワンライナー実行中はCtrl-Cを握りつぶす」という処理を追加しました(もちろん 2023.01.12 #1 に書いたサイトを見て SetConsoleCtrlHandler の使い方を理解しました)。これのおかげで、実行中にCtrl-Cを押しても、1ec.exeは死なないでプロンプトに帰れるのです。これは超便利です!
1ec>for (;;) prs "hello"
hellohellohellohellohellohellohellohellohellohello...
(中略)
hello^C
1ec>
つまりこんなことができるようになったわけです。やったー!
-この無限ループを書いても怖くないっていうのは、きっと小学生に教えるときには重宝すると思います。・・・いやそうでなくても、私自身がすごく便利になったのですけどね。
-そもそもシェルスクリプトなんていらないというか、こんなものはないほうがいいものかもしれません。C言語でちょっと書けば(=system関数を使いまくれば)、簡単に同等以上のことはできるのです。それなのにシェルスクリプトがあるということは、それだけC言語が手軽ではなかったということなんだと思います。・・・そうなら、C言語を手軽にすれば万事解決だったのに、なぜかシェルスクリプトという別言語を作って問題の解決を試みたわけです。これはよくない!
--まあ、現代はRubyやPythonのコマンドプロンプトを立ち上げておいて、その中で全部やるのかもしれないけど。
//-私は[[a21_txt01]]のHLシリーズとかその後継のHLXを作って使っているうちに、インタプリタ型の言語がいいなあってつくづく思うようになりました(いやまあ、それ以前からそんな風には思っていましたが)。コンパイルとか面倒なのです。
// どんな開発手順を踏めば、C言語から楽して自作言語に移行できるか?共存?
* こめんと欄
#comment