buntan-pc #2
(0)
2025.04.11 Fri #0
- 今のbuntan-pcにはasという専用のアセンブラがあるのだけど、これの互換品をささっと作ってしまいたい。これが当面の目標。
- そのために、どういうソースを渡せばどういうバイナリがでてくるかという、テストセットが欲しい。それがあればはかどる。
- 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()だけだな。どこかに落ちてないかな。
- 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"でオープンするようにしたら、完璧に出力結果が一致するようになった。
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
- インタプリタやコンパイラを作っていると、式の評価の処理が必要になります。例としては、a21_txt01_7の、expr()+exprSub()+exprSub1()みたいな処理です。
- 私が上記の処理を書いたのは2021年で、その後、数回の改良を加えて、もっときれいにすっきりと書けるようになりました(私だって少しは成長するのです)。
- とはいえ、言語を作るたびに似たような処理を書くというのはいかにも非効率だと思いました。インタプリタでもコンパラでもどちらの用途でも使えて、整数でも浮動小数でもString型でもユーザ定義のオブジェクトでも何でもいけるような、そういう汎用的なプログラムを一度作るべきなのです。そうすれば、その後は似たような処理を書かずに済みます。
- まずどのくらいの汎用性を持たせるかを考えました。
- パースのアルゴリズムは規定しないで、言語側に裁量を持たせる。
- 演算子の優先順位について。C言語に規定されている演算子については、基本的にはC言語と同じだとする。C言語とあまりに違うルールを採用すると、もはや混乱して使いこなせない気がするので。
- とはいえ、bitAndやbitOrなどのbit演算は比較演算子よりも優先されてほしいという気はする。しかしそれでもここを変更するとややこしいので、優先順位の異なる新たな演算子を追加してbitAndやbitOrを実現するのが一番スマートかなとも思う。・・・ということで基本的に既存の演算子はそのまま使うイメージになる。
- だから演算子の追加が自由かつ低コストでできればよい。
- どんな型をどうサポートするのかは言語側の裁量なので、このライブラリでは何も決めずに丸投げする。
- そういうことを考えて、適当にいい感じに作ってみました。
- 結局こんな感じになりました。
AClass(ExprDriver) {
const char *s, *s0; // もっともシンプルに行くなら、これだけでやれる. もちろんもっと複雑でもよい(言語側で好きなように決める).
// これはExprDriverの内部状態を保持するためのクラス.
};
AClass(ExprObject) { intptr_t typ; ... }; // 言語側で好きなように決める.
ExprObject *ExprDriver_func(ExprDriver *driver, intptr_t func, ...) { ... } // 話を分かりやすくするためにいったん省略.
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を指定しておけば問題ありません。