a25_buntan02
の編集
https://essen.osask.jp/?a25_buntan02
[
トップ
] [
編集
|
差分
|
バックアップ
|
添付
|
リロード
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
-- 雛形とするページ --
BracketName
EssenRev4
FormattingRules
FrontPage
Help
InterWiki
InterWikiName
InterWikiSandBox
K
MenuBar
PHP
PukiWiki
PukiWiki/1.4
PukiWiki/1.4/Manual
PukiWiki/1.4/Manual/Plugin
PukiWiki/1.4/Manual/Plugin/A-D
PukiWiki/1.4/Manual/Plugin/E-G
PukiWiki/1.4/Manual/Plugin/H-K
PukiWiki/1.4/Manual/Plugin/L-N
PukiWiki/1.4/Manual/Plugin/O-R
PukiWiki/1.4/Manual/Plugin/S-U
PukiWiki/1.4/Manual/Plugin/V-Z
RecentDeleted
SDL2_01
SandBox
WikiEngines
WikiName
WikiWikiWeb
YukiWiki
a21
a21_acl01
a21_bbs01
a21_challengers
a21_count
a21_edu01
a21_edu02
a21_edu03
a21_edu04
a21_edu05
a21_edu06
a21_edu07
a21_edu08
a21_edu09
a21_edu10
a21_edu11
a21_hlx000
a21_hlx001
a21_hlx001_1
a21_hlx001_2
a21_hlx001_3
a21_hlx002
a21_hlx002_1
a21_hlx003
a21_hlx003_1
a21_hlx004_1
a21_memo01
a21_opt
a21_opt02
a21_opt03
a21_p01
a21_special
a21_tl9a
a21_todo
a21_txt01
a21_txt01_10
a21_txt01_1a
a21_txt01_2
a21_txt01_2a
a21_txt01_2b
a21_txt01_3
a21_txt01_4
a21_txt01_5
a21_txt01_6
a21_txt01_6a
a21_txt01_7
a21_txt01_8
a21_txt01_8a
a21_txt01_9
a21_txt01_9a
a21_txt02
a21_txt02_10
a21_txt02_10a
a21_txt02_10b
a21_txt02_11
a21_txt02_11a
a21_txt02_12
a21_txt02_12a
a21_txt02_12b
a21_txt02_1a
a21_txt02_1b
a21_txt02_2
a21_txt02_2a
a21_txt02_3
a21_txt02_3a
a21_txt02_4
a21_txt02_4a
a21_txt02_5
a21_txt02_5a
a21_txt02_6
a21_txt02_6a
a21_txt02_6b
a21_txt02_6b_rev0
a21_txt02_6x
a21_txt02_7
a21_txt02_7a
a21_txt02_8
a21_txt02_8a
a21_txt02_9
a21_txt02_9a
a22_acl2_01
a22_acl2_02
a22_edu12
a22_intro01
a22_intro02
a22_intro03
a22_memman01
a22_memman02
a22_memman03
a22_memman04
a22_memman05
a22_memman06
a22_memman07
a22_memo01
a22_mingw_debug
a22_txt03
a22_txt03_1a
a22_txt03_1b
a22_txt03_2
a22_txt03_2a
a22_ufcs01
a23_bbs
a23_ec001
a23_ec002
a23_intro00
a23_intro000
a23_intro01
a23_intro02
a23_intro03
a23_intro04
a23_intro05
a23_intro06
a23_intro07
a23_intro08
a23_intro09
a23_intro10
a23_intro10wk1
a23_intro10wk2
a23_intro10wk3
a23_intro11
a23_intro12
a23_intro13
a23_intro13wk1
a23_intro14
a23_intro15
a23_intro16
a23_intro17
a23_intro17wk1
a23_intro18
a23_intro19
a23_intro90
a23_intro91
a23_neopixel1
a23_os01
a23_useSelfMade
a23_usm001
a23_usm002
a23_usm003
a23_usm004
a23_usm005
a23_usm006
a23_usm007
a23_usm008
a23_usm009
a24_AMap11
a24_AMapSim11
a24_AMemFile
a24_AMemMan
a24_aErrExit
a24_aFnv
a24_aOsFunc
a24_aQSort
a24_aXorShift32
a24_acl1T_doc01
a24_acl1Tiny
a24_acpp0
a24_buntan01
a24_cMin
a24_getTyp
a24_goodvalues
a24_idea001
a24_longdef
a24_memo01
a24_memo02
a24_osc20240310
a24_osc20241026
a24_picoLcd13
a24_picoTrain1
a24_programs
a24_raspberrypi01
a24_raspberrypi02
a24_schedule
a24_spc2tab
a24_tab2spc
a24_useSelfMade
a25_acl3
a25_buntan02
a25_buntan03
a25_buntan04
a25_buntan05
a25_kcas01
a25_kharc01
a25_kharc02
a25_kharc03
a25_kharc04
a25_kharc05
a25_kharc06
a25_kharcs1
a25_kharcs2
a25_kharcs3
a25_kharcs4
a25_kharcs5
a25_kharcs6
a25_kharcs7
a25_kharcs8
a25_kharcs9
aclib00
aclib01
aclib02
aclib03
aclib04
aclib05
aclib06
aclib07
aclib08
aclib09
aclib10
aclib11
aclib12
aclib13
aclib14
aclib15
aclib16
aclib17
aclib18
aclib19
aclib20
aclib21
aclib22
aclib23
aclib24
aclib25
aclib_bbs
arm64_01
avm0001
edu0001
edu0002
edu0003
esb02b_hrb
esb_dbg
esbasic0001
esbasic0002
esbasic0003
esbasic0004
esbasic0005
esbasic0006
esbasic0007
esbasic0008
esbasic0009
esbasic0010
esbasic0011
esbasic0012
esbasic0013
esbasic0014
esbasic0015
esbasic0016
esbasic0017
esbasic02a
esc0001
escm0001
essen_hist
esvm0001
esvm0002
esvm0003
esvm0004
esvm0005
esvm0006
esvm_i0
hh4a
idea0001
idea0002
idea0003
impressions
jck_0000
jck_0001
kawai
kbcl0_0000
kbcl0_0001
kbcl0_0002
kbcl0_0003
kbcl0_0004
kbcl0_0005
kbcl0_0006
kbcl0_0007
kclib1_0000
kclib1_0001
kclib1_0002
kclib1_0003
kclib1_0004
kclib1_0005
kclib1_0006
kclib1_0007
kclib1_0008
kclib1_0009
kclib1_0010
kpap0001
members
memo0001
osask4g
osask4g_r2
p20200311a
p20200610a
p20200610b
p20200624a
p20200711a
p20200716a
p20250813a
p20250813b
p20250813c
p20250815a
p20250903a
p20251006a
page0001
page0002
page0003
page0004
page0005
page0006
page0007
page0008
page0009
page0010
page0011
page0012
page0013
page0014
page0015
page0016
page0017
page0018
page0019
page0020
page0021
page0022
page0023
populars
seccamp
seccamp2019
sechack
sechack2019
seclang01
sh3_2020
sh3_2020_kw
sh3_2020_nk
sh3_2021_kw
sh3_2021_nk
sh3_2022_kw
sh3_2023_kw
sh3_2024_kw
sh3_2025_kw
sh3_kw_hist
termux001
termux002
text0001
text0001a
text0002
text0002a
text0003
text0004
text0005
text0006
text0006a
text0007
text0008
text0010
text0011
text0012
text0013
text0014
text0015
text0016
text0017
text0018
text0019
text0020
text0021
tl1c
tl2c
tl3c
tl3d
* buntan-pc #2 -(by [[K]], 2025.04.11) ** (0) -https://github.com/buntan-pc/ -これにかかわる開発の話 ** 2025.04.11 Fri #0 -今のbuntan-pcにはuasという専用のアセンブラがあるのだけど、これの互換品をささっと作ってしまいたい。これが当面の目標。 -そのために、どういうソースを渡せばどういうバイナリがでてくるかという、テストセットが欲しい。それがあればはかどる。 -uchanさんに、「・・・ということでテストセットを3組くらいちょうだい!」と頼んだら、「それくらい公開済みのCコンパイラとアセンブラがあれば自分でできるでしょ」といわれて、確かにそれもそうだと思った。ということでやってみる。こちらの環境はWindows+MS-C。 -uas.exeを作っていたらコンパイルエラーが出た。「//」のコメントの末尾が「取得」になっていると、次の行までコメント扱いされて、コンパイルエラーがいっぱい出る。半角ピリオドを追加してVCをなだめる。UTF-8で書かれたテキストなのに、なんでそうなるのか分からないけど、まあ対処法は簡単なので問題はない。 -上記のコメント問題を直したら、7個の警告が出るだけでビルドは問題なくできた。 -なるほどね、MS-Cには「/utf-8」っていうオプションがあるのかー。これを付けたらコメントいじらなくてもおとなしくなった。 -次はucc.exe。 -まずmakeを用意するのが面倒だったので以下のucc.cを作って代用。 #include "main.c" #include "ast.c" #include "insn.c" #include "symbol.c" #include "token.c" #include "type.c" -問題は2つ。 --libgen.hがないので、dirname()がない。 --以下の記述を嫌がる。 struct GenContext gen_ctx = { parse_ctx.scope, 0, 0, 0, {}, {-1, -1}, 0, {}, print_ast, 0 }; -さてどうしたものか。 0, 0, 0, {0}, {-1, -1}, 0, {0}, print_ast, 0 --にしたらとりあえず文句を言わなくなった。 -じゃあ残るはdirname()だけだな。どこかに落ちてないかな。 --なるほど、こういう仕様なのか。 https://surf.st.seikei.ac.jp/~nakano/JMwww/html/LDP_man-pages/man3/dirname.3.html -Cursorさんに「<libgen.h>のdirnameを作ってください。」って頼んでみたら、作ってくれた。 #include <stdio.h> #include <string.h> char* my_dirname(char* path) { static char buffer[256]; char* last_slash; // パスがNULLまたは空の場合、"."を返す if (path == NULL || *path == '\0') { return "."; } // パスをコピー strncpy(buffer, path, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\0'; // 最後のスラッシュを探す last_slash = strrchr(buffer, '/'); // スラッシュが見つからない場合、"."を返す if (last_slash == NULL) { return "."; } // スラッシュの後ろを終端文字に置き換える if (last_slash != buffer) { *last_slash = '\0'; } else { // ルートディレクトリの場合 *(last_slash + 1) = '\0'; } return buffer; } --とりあえずこれでいけそうだな。 -これでucc.exeはビルドできるようになったけど、できたucc.exeは挙動が怪しい。じゃあ次の作業はデバッグだな。 -いやでも、uas.exeは問題なく動いているっぽいし、ucc/testsの中にarray.sとかもあるから、とりあえずuccはデバッグしなくてもいいのかー。 ** 2025.04.11 Fri #1 -ある程度テストしてみたところ、手元でビルドしたuas.exeは完璧に動いているっぽい。 ** 2025.04.14 Mon #0 -とりあえずK版のアセンブラは半分くらいできた。 -今のところ、289行。 -本家は section .data の次に section .text を書くというルールで、セクションをこまめに切り替えたりはできないけれど、今作っている版では、セクションの宣言順序は自由だし、途中で何度もセクションを切り替えられる。 ** 2025.04.15 Tue #0 -array.sくらいならアセンブルできるようになった!(かも)。 -まだ出力部を書いてないので、メモリの中でアセンブルできているだけ。バグっているかどうかは分からない。 -数値計算を本家よりは頑張ったので、 push lb3-lb2+1 くらいならできるはず(バグってなければ)。つまりカッコなしの加減算ができる。 -今は356行。 ** 2025.04.15 Tue #1 -db命令にも対応して、サンプルの.sファイルは全部アセンブルできるようになった。 -あとは出力部をかかないとな・・・。 ** 2025.04.15 Tue #2 -出力部もできた。ちょっとバクもあったけど、それも直して、ついに本家のアセンブラと同じバイナリを出せるようになった! -今は408行。まあでも行数はそんなに重要じゃない。1行に詰め込めばどうにでもなるのだから。 -一番苦戦したデバッグは、fopen(exe, "wb")が失敗してNULLを返すというもの。・・・いやいや、なんで失敗するのさ!readじゃないよwriteだよ、普通失敗しないじゃないか! --BZエディタで開いているファイルをfopenで書き込みオープンすると、fp==NULLにされる。なるほど、アクセス権が取れなかったんだね。 --これに気づくまでに30分くらい悩んだ。まさかここで失敗しているなんて思わなかったので・・・。 -あとは些細な問題として、途中から本家が出力している実行ファイルと一致しないで、1バイトずれるという問題があった。でもそれは数秒でわかった。本家は"w"でオープンしていたから、テキストモードになっていたわけだ。それで0x0aを出力すると勝手に0x0dが追加されてしまう。ということで、本家も"wb"でオープンするようにしたら、完璧に出力結果が一致するようになった。 -やったね!(明日upload予定) ** 2025.04.16 Wed #0 -今回アセンブラを作るために使ったaclminiというライブラリについて、自分用のメモ。 Ai intptr_t のtypedef (頻出なので短く書けるようにした、以下同じ動機) Ap void * のtypedef Av void のtypedef Auc unsigned char のtypedef Asc signed char のtypedef AStr char * のtypedef As static の#define AClass(Abc) { ... }; → structしてtypedefしてくれるマクロ aSz(Abc) (intptr_t) sizeof(Abc) の代わり (sizeofはunsignedなので使いにくいから) → (追記)のちに ASz に改名した AMlc : AMalloc - 汎用メモリアロケータ・インターフェース AMlcStd : AMlc Standard - malloc/free/reallocを使って作ったAMlc AMlcStdNumSz : AMlcStd Number/Size - mallocしたブロック数と総mallocバイト数をいつでも確認できるようにしたAMlcStd (メモリリークがないことを確認しやすい) ATokenMgr : ATokenManager - 文字列に対して出現順に0,1,2,...と番号を付けるためのクラス (内部で二分探索するので検索は速い) AExpMem : AExpandableMemory - 可変長配列 -aSzはASzにした方がいいかもしれない。そうすればAで始まる名前がこのライブラリ所属だとわかる。aで始まる名前は解放される。 --(追記)ということで aSz は廃止して ASz にした。 ** 2025.04.16 Wed #1 -https://essen.osask.jp/files/kuas00a.zip (4.79KB) --ソースファイルのみです。書いてないけどライセンスはKL-01です。buntan-pcにマージされたものは、MITライセンスになります。 --コンパイルの際は、 kuas.c のみコンパイルすればいいです。aclmini.cは勝手にインクルードされます。 --うわー、ソースコードだけでzip化すると5KB未満かー。小さいなー。 -今回かなり短期間で書けたわけですが、それで思ったのは、もう整合性の取れた仕様が決まっていて、それに合わせて作るだけでよければ、それほど大変じゃないってことなんだろうと思います。それに本家のuasはシンプルに作れるように仕様が工夫されているのもあると思います。 ** 2025.04.16 Wed #2 -「--pmem」「--dmem」オプションに対応してほしいと言われたので対応しました。 -https://essen.osask.jp/files/kuas01a.zip (4.92KB) --ソースファイルのみです。書いてないけどライセンスはKL-01です。buntan-pcにマージされたものは、MITライセンスになります。 --コンパイルの際は、 kuas.c のみコンパイルすればいいです。aclmini.cは勝手にインクルードされます。 ** 2025.04.18 Fri #0 -const-expr.sにも対応してほしいと言われたので対応しました。行数的には10行の増加になりました。 -https://essen.osask.jp/files/kuas02a.zip (5.13KB) --ソースファイルのみです。書いてないけどライセンスはKL-01です。buntan-pcにマージされたものは、MITライセンスになります。 --コンパイルの際は、 kuas.c のみコンパイルすればいいです。aclmini.cは勝手にインクルードされます。 ** 2025.04.19 Sat #0 -今日はkuasには直接関係しない話を書きます。 -インタプリタやコンパイラを作っていると、式の評価の処理が必要になります。例としては、[[a21_txt01_7]]の、expr()+exprSub()+exprSub1()みたいな処理です。 -私が上記の処理を書いたのは2021年で、その後、数回の改良を加えて、もっときれいにすっきりと書けるようになりました(私だって少しは成長するのです)。 -とはいえ、言語を作るたびに似たような処理を書くというのはいかにも非効率だと思いました。インタプリタでもコンパラでもどちらの用途でも使えて、整数でも浮動小数点でもString型でもユーザ定義のオブジェクトでも何でもいけるような、そういう汎用的なプログラムを一度作るべきなのです。そうすれば、その後は似たような処理を書かずに済みます。 -まずどのくらいの汎用性を持たせるかを考えました。 --パースのアルゴリズムは規定しないで、言語側に裁量を持たせる。 --演算子の優先順位について。C言語に規定されている演算子については、基本的にはC言語と同じだとする。C言語とあまりに違うルールを採用すると、もはや混乱して使いこなせない気がするので。 --とはいえ、bitAndやbitOrなどのbit演算は比較演算子よりも優先されてほしいという気はする。しかしそれでもここを変更するとややこしいので、優先順位の異なる新たな演算子を追加してbitAndやbitOrを実現するのが一番スマートかなとも思う。・・・ということで基本的に既存の演算子はそのまま使うイメージになる。 --だから演算子の追加が自由かつ低コストでできればよい。 --どんな型をどうサポートするのかは言語側の裁量なので、このライブラリでは何も決めずに丸投げする。 -そういうことを考えて、適当にいい感じに作ってみました。 -結局こんな感じになりました。 AClass(ExprDriver) { // これはExprDriverの内部状態を保持するためのクラス. const char *s, *s0; // もっともシンプルに行くなら、これだけでやれる. もちろんもっと複雑でもよい(言語側で好きなように決める). }; AClass(ExprObject) { intptr_t typ; ... }; // 言語側で好きなように決める. ExprObject *ExprDriver_func(ExprDriver *driver, intptr_t func, ExprObject *obj0, ExprObject *obj1) { ... } // 話を分かりやすくするためにいったん省略. int main(int argc, const char **argv) { if (argc >= 2) { ExprDriver driver; driver.s = argv[1]; ExprObject *result = (ExprObject *) AExpr(ExprDriver_func, &driver, 999); // ここでresultを表示. } return 0; } -これだけで行けます。ExprDriver_func()が言語処理系依存の処理を全部やります。AExpr()から見るとコールバック関数です。 -AExpr()の最後の999は演算子の優先順位です。優先度999までの演算をすべて完了してから結果をかえすように指示しています。これは 1+2*3+4 みたいな式があって、「1+2*」まで処理したときに、次に3を取ってくる処理があるわけですが、この場合、3+4ではなく、3で打ち切って取ってこないと次の乗算の結果がおかしくなるわけです。このように式の途中までで評価を打ち切る必要が内部的にはあるので、演算子の優先順位を最後に書くことになっています(AExprはAExprを再帰的に呼び出して式の評価を実現しています)。・・・いきなり何を言っているのか分からなかったかと思いますが、基本的に999を書いておけば全部正しく最後まで処理されるので、気にしないで999を指定しておけば問題ありません。 -こんな ExprDriver_func() に丸投げばかりの仕様なので、 ExprDriver_func() が複雑になって、結局誰も得しないってことになりそうなわけですが、しかしドライバ側で優先順位がどうなっているかはまったく気にしなくてよくなります。AExprが矢継ぎ早に正しい順序で呼び出してくるので、受け身に徹してこなしていれば、自然にすべて無駄なく処理されます。これが気持ちいいのです!(笑)。余計なことで頭を使わずに済みます。 func==0: 対象となる数式から次の1トークンを取ってくるだけの処理です。ExprObjectで返します。 演算子なのか何らかの値を持つオブジェクトなのかは、ExprObject内の先頭にあるtypメンバの値で認識されます。 func==1: 指定されたExprObjectをメモリ開放します。どんな開放処理が必要なのかAExprにはわからないので、ドライバ関数に依頼する形になっています。 まあたいていはfreeするだけの簡単な処理になります。 func==2: 直前にfunc==0で読み取ったトークンを、押し戻します。ungetc()みたいなやつです。 こういうことをできるようにするもっとも簡単な方法は、func==0のときに、元のsの値をs0に保存しておくことです。 このやり方なら、func==2が来た時に、s = s0; を実行するだけでおしまいです。 func==3: リザーブです。今のところこの呼び出しはありません。 func==4: 単項演算子を新規に追加したい場合、もしくは既存の単項演算子の挙動をオーバーライドしたい場合に、この関数で処理します。 追加も変更もないなら、ただ0を返しておけばいいです。 func==5: 二項演算子、三項演算子などを新規に追加したい場合、もしくは既存の演算子の挙動をオーバーライドしたい場合に、この関数で処理します。 追加も変更もないなら、ただ0を返しておけばいいです。 func==AExpr_Add: 二項演算子「+」による加算です。2つのExprObejectを引数として受け取り、演算して、結果が入ったExprObjectを返します。 他にも演算子の数だけ同様の呼び出しがあります。対応したくない演算子に関しては、演算結果ではなくエラーオブジェクトを返せば、 問題なくエラー処理されます。・・・だから処理したい演算子だけ書いておいて、最後に「それ以外は全部エラーを返す」と書くわけです。 ** 2025.04.20 Sun #0 -uchanさんのComProcのCPUのアセンブラで書かれたプログラムの多くを、一般的なC言語プログラムに移植するアルゴリズムを思いついた「かもしれない」。 -同じアルゴリズムはたぶんx86とかx64とかARMとかAArch64とかRISC-Vのアセンブラにも適用できそう。 --エミュレータのいらない世界が実現したらいいなあ。 -これが実現したら、CPUを自作したときに、たくさんのプログラムを簡単に移植してくることができる・・・とかできたらいいなあ。 -(追記)もう少しだけ詳しい話を書いてみる。 -たいていのアセンブラでは、CALL命令(もしくはそれに相当する命令)がある。これはC言語で言うと関数呼び出しに相当する。 -これを一般的なC言語プログラムでどう表現するかという問題があった。 -まず前提として、アセンブラのラベルにはCALL用かJMP用かという区別はない。通常はCALLで呼ぶようなラベルを最適化としてJMPで分岐する場合すらある。 --これはCALL命令の直後にRET命令があるときとかに、よく用いられる最適化。 -こういうことをされると、「CALLされることがあるラベルかどうかを集計して、CALLされるものは関数としてC言語化する」という戦略が使えない。なぜならC言語では、関数に対してgotoはできないから。 --これに対しては、関数に対するJMPをCALL+RETに変換すればいいかなと考えたこともあったけど、スタックの深さとかが狂ってくるので、たぶんうまくいかない。 -そもそもC言語においてはgoto用のラベルと関数はかなり異なる。関数はスタックフレームの構築処理などもあり、そんなに簡単に代用できる話ではない。 -そうなると、CALL命令はリターン用のラベルを宣言してその値をスタックに積み(もしくはしかるべきレジスタに代入し)、その上でJMPする、という処理に変換するのがよさそうに思える。 --[註]RISC系の一部のCPUではCALL/RETでスタックを使わず、専用のレジスタにリターン先を代入する。関数側でこのレジスタを退避・復元することで、関数呼び出しネストを実現する。 -でもこれは難しい。goto用のラベル値を取得する方法がない。gccの独自拡張にはあるが、それだとgccでしかコンパイルできないことになる。 --http://cms.phys.s.u-tokyo.ac.jp/~naoki/CIPINTRO/gccextend.html --というかgccを作った人は、本当によく考えている。この機能はプログラミングの可能性を大きく広げてくれる。 -CALL命令はほぼすべてのアセンブラプログラムに出てくるくらいのメジャーな命令なので、これをコンバートできないなら「一般的なC言語プログラムに移植する」という基本アイデアは捨てなきゃいけない。 -ここからが今回の思い付き。プログラム全体を巨大なswitch-caseでラッピングする。プログラムの一番最初にswitchを置く。そして「goto用のラベル値」ではなく、「switchで分岐するための値」を取得するという方式にする。この値を適当な変数に代入して、プログラムの先頭にgotoすれば、swicth-caseによって狙った場所に分岐できる。 --うーん、前提の部分も含めて、この説明じゃ何を言いたいのかよく分からないだろうなあ。実際に作って見せるのが一番早そう。 ** 2025.04.20 Sun #1 -一方で、安野さんたちのvibe codingの話とかを見ていると、私なんかがあれこれ考えなくても、全部AIが何とかしてくれる未来の方が先に来るんじゃないかなっていう気分になる・・・。 --https://x.com/takahiroanno/status/1913121506273710241 ** 2025.04.21 Mon #0 -たまに、どうやってプログラミングの勉強をしたらいいですか?と聞かれます。私の考えを書きます。 -まずとにかくプログラムを書きます。OSに関心がある時はOSを何個も書きます。データ圧縮に関心がある時は圧縮プログラムを何個も書きます。プログラミング言語に関心がある時は言語を何個も作ります。そうやって色々作っていると、自分の作るプログラムには共通部分があることが分かります。それはライブラリ化して、何度も何度も書かないでいいようにするべきです。・・・こういうことを繰り返していると、自分の開発力が底上げされてきます。 -私は自分が作ったプログラムのすべてを常用しているわけではありません。それでもいいのです。でも反省はします。なぜ使わないのかと。プログラムを書いても使わないなら、そんなの作らない方がいいじゃないですか。ライブラリだって使わないなら作らなくていいじゃないですか。・・・この反省を生かして次の開発をすると、どんどんうまくなっていくのです。 // subはp0,p1だけではなく、opの情報も必要. 具体的には (type) とか。 // cast演算子(typオブジェクト, valオブジェクト)にできればとても良いが、できるだろうか? // (typ)を検出したら、まずtypをオブジェクトとして送出する。次にcast演算子を送出する。残りは普通に結合する。これで行けるのでは? // ?の3項演算子はどうするのか。まず?が来る。スタックする。:が来る。スタックトップを見て処理する。 // 単項演算子と二項演算子の違いは、実際の項の数ではなく、演算子の前にオブジェクトが必要か必要ではないかの差でしかない。
タイムスタンプを変更しない
* buntan-pc #2 -(by [[K]], 2025.04.11) ** (0) -https://github.com/buntan-pc/ -これにかかわる開発の話 ** 2025.04.11 Fri #0 -今のbuntan-pcにはuasという専用のアセンブラがあるのだけど、これの互換品をささっと作ってしまいたい。これが当面の目標。 -そのために、どういうソースを渡せばどういうバイナリがでてくるかという、テストセットが欲しい。それがあればはかどる。 -uchanさんに、「・・・ということでテストセットを3組くらいちょうだい!」と頼んだら、「それくらい公開済みのCコンパイラとアセンブラがあれば自分でできるでしょ」といわれて、確かにそれもそうだと思った。ということでやってみる。こちらの環境はWindows+MS-C。 -uas.exeを作っていたらコンパイルエラーが出た。「//」のコメントの末尾が「取得」になっていると、次の行までコメント扱いされて、コンパイルエラーがいっぱい出る。半角ピリオドを追加してVCをなだめる。UTF-8で書かれたテキストなのに、なんでそうなるのか分からないけど、まあ対処法は簡単なので問題はない。 -上記のコメント問題を直したら、7個の警告が出るだけでビルドは問題なくできた。 -なるほどね、MS-Cには「/utf-8」っていうオプションがあるのかー。これを付けたらコメントいじらなくてもおとなしくなった。 -次はucc.exe。 -まずmakeを用意するのが面倒だったので以下のucc.cを作って代用。 #include "main.c" #include "ast.c" #include "insn.c" #include "symbol.c" #include "token.c" #include "type.c" -問題は2つ。 --libgen.hがないので、dirname()がない。 --以下の記述を嫌がる。 struct GenContext gen_ctx = { parse_ctx.scope, 0, 0, 0, {}, {-1, -1}, 0, {}, print_ast, 0 }; -さてどうしたものか。 0, 0, 0, {0}, {-1, -1}, 0, {0}, print_ast, 0 --にしたらとりあえず文句を言わなくなった。 -じゃあ残るはdirname()だけだな。どこかに落ちてないかな。 --なるほど、こういう仕様なのか。 https://surf.st.seikei.ac.jp/~nakano/JMwww/html/LDP_man-pages/man3/dirname.3.html -Cursorさんに「<libgen.h>のdirnameを作ってください。」って頼んでみたら、作ってくれた。 #include <stdio.h> #include <string.h> char* my_dirname(char* path) { static char buffer[256]; char* last_slash; // パスがNULLまたは空の場合、"."を返す if (path == NULL || *path == '\0') { return "."; } // パスをコピー strncpy(buffer, path, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\0'; // 最後のスラッシュを探す last_slash = strrchr(buffer, '/'); // スラッシュが見つからない場合、"."を返す if (last_slash == NULL) { return "."; } // スラッシュの後ろを終端文字に置き換える if (last_slash != buffer) { *last_slash = '\0'; } else { // ルートディレクトリの場合 *(last_slash + 1) = '\0'; } return buffer; } --とりあえずこれでいけそうだな。 -これでucc.exeはビルドできるようになったけど、できたucc.exeは挙動が怪しい。じゃあ次の作業はデバッグだな。 -いやでも、uas.exeは問題なく動いているっぽいし、ucc/testsの中にarray.sとかもあるから、とりあえずuccはデバッグしなくてもいいのかー。 ** 2025.04.11 Fri #1 -ある程度テストしてみたところ、手元でビルドしたuas.exeは完璧に動いているっぽい。 ** 2025.04.14 Mon #0 -とりあえずK版のアセンブラは半分くらいできた。 -今のところ、289行。 -本家は section .data の次に section .text を書くというルールで、セクションをこまめに切り替えたりはできないけれど、今作っている版では、セクションの宣言順序は自由だし、途中で何度もセクションを切り替えられる。 ** 2025.04.15 Tue #0 -array.sくらいならアセンブルできるようになった!(かも)。 -まだ出力部を書いてないので、メモリの中でアセンブルできているだけ。バグっているかどうかは分からない。 -数値計算を本家よりは頑張ったので、 push lb3-lb2+1 くらいならできるはず(バグってなければ)。つまりカッコなしの加減算ができる。 -今は356行。 ** 2025.04.15 Tue #1 -db命令にも対応して、サンプルの.sファイルは全部アセンブルできるようになった。 -あとは出力部をかかないとな・・・。 ** 2025.04.15 Tue #2 -出力部もできた。ちょっとバクもあったけど、それも直して、ついに本家のアセンブラと同じバイナリを出せるようになった! -今は408行。まあでも行数はそんなに重要じゃない。1行に詰め込めばどうにでもなるのだから。 -一番苦戦したデバッグは、fopen(exe, "wb")が失敗してNULLを返すというもの。・・・いやいや、なんで失敗するのさ!readじゃないよwriteだよ、普通失敗しないじゃないか! --BZエディタで開いているファイルをfopenで書き込みオープンすると、fp==NULLにされる。なるほど、アクセス権が取れなかったんだね。 --これに気づくまでに30分くらい悩んだ。まさかここで失敗しているなんて思わなかったので・・・。 -あとは些細な問題として、途中から本家が出力している実行ファイルと一致しないで、1バイトずれるという問題があった。でもそれは数秒でわかった。本家は"w"でオープンしていたから、テキストモードになっていたわけだ。それで0x0aを出力すると勝手に0x0dが追加されてしまう。ということで、本家も"wb"でオープンするようにしたら、完璧に出力結果が一致するようになった。 -やったね!(明日upload予定) ** 2025.04.16 Wed #0 -今回アセンブラを作るために使ったaclminiというライブラリについて、自分用のメモ。 Ai intptr_t のtypedef (頻出なので短く書けるようにした、以下同じ動機) Ap void * のtypedef Av void のtypedef Auc unsigned char のtypedef Asc signed char のtypedef AStr char * のtypedef As static の#define AClass(Abc) { ... }; → structしてtypedefしてくれるマクロ aSz(Abc) (intptr_t) sizeof(Abc) の代わり (sizeofはunsignedなので使いにくいから) → (追記)のちに ASz に改名した AMlc : AMalloc - 汎用メモリアロケータ・インターフェース AMlcStd : AMlc Standard - malloc/free/reallocを使って作ったAMlc AMlcStdNumSz : AMlcStd Number/Size - mallocしたブロック数と総mallocバイト数をいつでも確認できるようにしたAMlcStd (メモリリークがないことを確認しやすい) ATokenMgr : ATokenManager - 文字列に対して出現順に0,1,2,...と番号を付けるためのクラス (内部で二分探索するので検索は速い) AExpMem : AExpandableMemory - 可変長配列 -aSzはASzにした方がいいかもしれない。そうすればAで始まる名前がこのライブラリ所属だとわかる。aで始まる名前は解放される。 --(追記)ということで aSz は廃止して ASz にした。 ** 2025.04.16 Wed #1 -https://essen.osask.jp/files/kuas00a.zip (4.79KB) --ソースファイルのみです。書いてないけどライセンスはKL-01です。buntan-pcにマージされたものは、MITライセンスになります。 --コンパイルの際は、 kuas.c のみコンパイルすればいいです。aclmini.cは勝手にインクルードされます。 --うわー、ソースコードだけでzip化すると5KB未満かー。小さいなー。 -今回かなり短期間で書けたわけですが、それで思ったのは、もう整合性の取れた仕様が決まっていて、それに合わせて作るだけでよければ、それほど大変じゃないってことなんだろうと思います。それに本家のuasはシンプルに作れるように仕様が工夫されているのもあると思います。 ** 2025.04.16 Wed #2 -「--pmem」「--dmem」オプションに対応してほしいと言われたので対応しました。 -https://essen.osask.jp/files/kuas01a.zip (4.92KB) --ソースファイルのみです。書いてないけどライセンスはKL-01です。buntan-pcにマージされたものは、MITライセンスになります。 --コンパイルの際は、 kuas.c のみコンパイルすればいいです。aclmini.cは勝手にインクルードされます。 ** 2025.04.18 Fri #0 -const-expr.sにも対応してほしいと言われたので対応しました。行数的には10行の増加になりました。 -https://essen.osask.jp/files/kuas02a.zip (5.13KB) --ソースファイルのみです。書いてないけどライセンスはKL-01です。buntan-pcにマージされたものは、MITライセンスになります。 --コンパイルの際は、 kuas.c のみコンパイルすればいいです。aclmini.cは勝手にインクルードされます。 ** 2025.04.19 Sat #0 -今日はkuasには直接関係しない話を書きます。 -インタプリタやコンパイラを作っていると、式の評価の処理が必要になります。例としては、[[a21_txt01_7]]の、expr()+exprSub()+exprSub1()みたいな処理です。 -私が上記の処理を書いたのは2021年で、その後、数回の改良を加えて、もっときれいにすっきりと書けるようになりました(私だって少しは成長するのです)。 -とはいえ、言語を作るたびに似たような処理を書くというのはいかにも非効率だと思いました。インタプリタでもコンパラでもどちらの用途でも使えて、整数でも浮動小数点でもString型でもユーザ定義のオブジェクトでも何でもいけるような、そういう汎用的なプログラムを一度作るべきなのです。そうすれば、その後は似たような処理を書かずに済みます。 -まずどのくらいの汎用性を持たせるかを考えました。 --パースのアルゴリズムは規定しないで、言語側に裁量を持たせる。 --演算子の優先順位について。C言語に規定されている演算子については、基本的にはC言語と同じだとする。C言語とあまりに違うルールを採用すると、もはや混乱して使いこなせない気がするので。 --とはいえ、bitAndやbitOrなどのbit演算は比較演算子よりも優先されてほしいという気はする。しかしそれでもここを変更するとややこしいので、優先順位の異なる新たな演算子を追加してbitAndやbitOrを実現するのが一番スマートかなとも思う。・・・ということで基本的に既存の演算子はそのまま使うイメージになる。 --だから演算子の追加が自由かつ低コストでできればよい。 --どんな型をどうサポートするのかは言語側の裁量なので、このライブラリでは何も決めずに丸投げする。 -そういうことを考えて、適当にいい感じに作ってみました。 -結局こんな感じになりました。 AClass(ExprDriver) { // これはExprDriverの内部状態を保持するためのクラス. const char *s, *s0; // もっともシンプルに行くなら、これだけでやれる. もちろんもっと複雑でもよい(言語側で好きなように決める). }; AClass(ExprObject) { intptr_t typ; ... }; // 言語側で好きなように決める. ExprObject *ExprDriver_func(ExprDriver *driver, intptr_t func, ExprObject *obj0, ExprObject *obj1) { ... } // 話を分かりやすくするためにいったん省略. int main(int argc, const char **argv) { if (argc >= 2) { ExprDriver driver; driver.s = argv[1]; ExprObject *result = (ExprObject *) AExpr(ExprDriver_func, &driver, 999); // ここでresultを表示. } return 0; } -これだけで行けます。ExprDriver_func()が言語処理系依存の処理を全部やります。AExpr()から見るとコールバック関数です。 -AExpr()の最後の999は演算子の優先順位です。優先度999までの演算をすべて完了してから結果をかえすように指示しています。これは 1+2*3+4 みたいな式があって、「1+2*」まで処理したときに、次に3を取ってくる処理があるわけですが、この場合、3+4ではなく、3で打ち切って取ってこないと次の乗算の結果がおかしくなるわけです。このように式の途中までで評価を打ち切る必要が内部的にはあるので、演算子の優先順位を最後に書くことになっています(AExprはAExprを再帰的に呼び出して式の評価を実現しています)。・・・いきなり何を言っているのか分からなかったかと思いますが、基本的に999を書いておけば全部正しく最後まで処理されるので、気にしないで999を指定しておけば問題ありません。 -こんな ExprDriver_func() に丸投げばかりの仕様なので、 ExprDriver_func() が複雑になって、結局誰も得しないってことになりそうなわけですが、しかしドライバ側で優先順位がどうなっているかはまったく気にしなくてよくなります。AExprが矢継ぎ早に正しい順序で呼び出してくるので、受け身に徹してこなしていれば、自然にすべて無駄なく処理されます。これが気持ちいいのです!(笑)。余計なことで頭を使わずに済みます。 func==0: 対象となる数式から次の1トークンを取ってくるだけの処理です。ExprObjectで返します。 演算子なのか何らかの値を持つオブジェクトなのかは、ExprObject内の先頭にあるtypメンバの値で認識されます。 func==1: 指定されたExprObjectをメモリ開放します。どんな開放処理が必要なのかAExprにはわからないので、ドライバ関数に依頼する形になっています。 まあたいていはfreeするだけの簡単な処理になります。 func==2: 直前にfunc==0で読み取ったトークンを、押し戻します。ungetc()みたいなやつです。 こういうことをできるようにするもっとも簡単な方法は、func==0のときに、元のsの値をs0に保存しておくことです。 このやり方なら、func==2が来た時に、s = s0; を実行するだけでおしまいです。 func==3: リザーブです。今のところこの呼び出しはありません。 func==4: 単項演算子を新規に追加したい場合、もしくは既存の単項演算子の挙動をオーバーライドしたい場合に、この関数で処理します。 追加も変更もないなら、ただ0を返しておけばいいです。 func==5: 二項演算子、三項演算子などを新規に追加したい場合、もしくは既存の演算子の挙動をオーバーライドしたい場合に、この関数で処理します。 追加も変更もないなら、ただ0を返しておけばいいです。 func==AExpr_Add: 二項演算子「+」による加算です。2つのExprObejectを引数として受け取り、演算して、結果が入ったExprObjectを返します。 他にも演算子の数だけ同様の呼び出しがあります。対応したくない演算子に関しては、演算結果ではなくエラーオブジェクトを返せば、 問題なくエラー処理されます。・・・だから処理したい演算子だけ書いておいて、最後に「それ以外は全部エラーを返す」と書くわけです。 ** 2025.04.20 Sun #0 -uchanさんのComProcのCPUのアセンブラで書かれたプログラムの多くを、一般的なC言語プログラムに移植するアルゴリズムを思いついた「かもしれない」。 -同じアルゴリズムはたぶんx86とかx64とかARMとかAArch64とかRISC-Vのアセンブラにも適用できそう。 --エミュレータのいらない世界が実現したらいいなあ。 -これが実現したら、CPUを自作したときに、たくさんのプログラムを簡単に移植してくることができる・・・とかできたらいいなあ。 -(追記)もう少しだけ詳しい話を書いてみる。 -たいていのアセンブラでは、CALL命令(もしくはそれに相当する命令)がある。これはC言語で言うと関数呼び出しに相当する。 -これを一般的なC言語プログラムでどう表現するかという問題があった。 -まず前提として、アセンブラのラベルにはCALL用かJMP用かという区別はない。通常はCALLで呼ぶようなラベルを最適化としてJMPで分岐する場合すらある。 --これはCALL命令の直後にRET命令があるときとかに、よく用いられる最適化。 -こういうことをされると、「CALLされることがあるラベルかどうかを集計して、CALLされるものは関数としてC言語化する」という戦略が使えない。なぜならC言語では、関数に対してgotoはできないから。 --これに対しては、関数に対するJMPをCALL+RETに変換すればいいかなと考えたこともあったけど、スタックの深さとかが狂ってくるので、たぶんうまくいかない。 -そもそもC言語においてはgoto用のラベルと関数はかなり異なる。関数はスタックフレームの構築処理などもあり、そんなに簡単に代用できる話ではない。 -そうなると、CALL命令はリターン用のラベルを宣言してその値をスタックに積み(もしくはしかるべきレジスタに代入し)、その上でJMPする、という処理に変換するのがよさそうに思える。 --[註]RISC系の一部のCPUではCALL/RETでスタックを使わず、専用のレジスタにリターン先を代入する。関数側でこのレジスタを退避・復元することで、関数呼び出しネストを実現する。 -でもこれは難しい。goto用のラベル値を取得する方法がない。gccの独自拡張にはあるが、それだとgccでしかコンパイルできないことになる。 --http://cms.phys.s.u-tokyo.ac.jp/~naoki/CIPINTRO/gccextend.html --というかgccを作った人は、本当によく考えている。この機能はプログラミングの可能性を大きく広げてくれる。 -CALL命令はほぼすべてのアセンブラプログラムに出てくるくらいのメジャーな命令なので、これをコンバートできないなら「一般的なC言語プログラムに移植する」という基本アイデアは捨てなきゃいけない。 -ここからが今回の思い付き。プログラム全体を巨大なswitch-caseでラッピングする。プログラムの一番最初にswitchを置く。そして「goto用のラベル値」ではなく、「switchで分岐するための値」を取得するという方式にする。この値を適当な変数に代入して、プログラムの先頭にgotoすれば、swicth-caseによって狙った場所に分岐できる。 --うーん、前提の部分も含めて、この説明じゃ何を言いたいのかよく分からないだろうなあ。実際に作って見せるのが一番早そう。 ** 2025.04.20 Sun #1 -一方で、安野さんたちのvibe codingの話とかを見ていると、私なんかがあれこれ考えなくても、全部AIが何とかしてくれる未来の方が先に来るんじゃないかなっていう気分になる・・・。 --https://x.com/takahiroanno/status/1913121506273710241 ** 2025.04.21 Mon #0 -たまに、どうやってプログラミングの勉強をしたらいいですか?と聞かれます。私の考えを書きます。 -まずとにかくプログラムを書きます。OSに関心がある時はOSを何個も書きます。データ圧縮に関心がある時は圧縮プログラムを何個も書きます。プログラミング言語に関心がある時は言語を何個も作ります。そうやって色々作っていると、自分の作るプログラムには共通部分があることが分かります。それはライブラリ化して、何度も何度も書かないでいいようにするべきです。・・・こういうことを繰り返していると、自分の開発力が底上げされてきます。 -私は自分が作ったプログラムのすべてを常用しているわけではありません。それでもいいのです。でも反省はします。なぜ使わないのかと。プログラムを書いても使わないなら、そんなの作らない方がいいじゃないですか。ライブラリだって使わないなら作らなくていいじゃないですか。・・・この反省を生かして次の開発をすると、どんどんうまくなっていくのです。 // subはp0,p1だけではなく、opの情報も必要. 具体的には (type) とか。 // cast演算子(typオブジェクト, valオブジェクト)にできればとても良いが、できるだろうか? // (typ)を検出したら、まずtypをオブジェクトとして送出する。次にcast演算子を送出する。残りは普通に結合する。これで行けるのでは? // ?の3項演算子はどうするのか。まず?が来る。スタックする。:が来る。スタックトップを見て処理する。 // 単項演算子と二項演算子の違いは、実際の項の数ではなく、演算子の前にオブジェクトが必要か必要ではないかの差でしかない。
テキスト整形のルールを表示する