a26_txt03
の編集
https://essen.osask.jp/?a26_txt03
[
トップ
] [
編集
|
差分
|
バックアップ
|
添付
|
リロード
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
-- 雛形とするページ --
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_acl4
a25_acl4_001
a25_acl4_002
a25_acl4_003
a25_acl4_log01
a25_acl4_log02
a25_acl4_log03
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
a26_txt03
a26_txt03_001
a26_txt03_002
a4_0001
a4_0002
a4_0003
a4_0004
a4_0005
a4_0006
a4_0007
a4_0008
a4_0009
a4_0010
a4_0011
a4_0012
a4_0013
a4_0014
a4_0016
a4_0017
a4_0018
a4_0019
a4_0020
a4_0021
a4_0022
a4_0023
a4_comments
a4_d0001
a4_d0002
a4_d0003
a4_d0004
a4_d0005
a4_d0006
a4_i01
a4_links
a4_log
a4_log00
a4_log04
a4_log05
a4_log06
a4_log07
a4_log08
a4_log09
a4_log10
a4_log11
a4_p0001
a4_p0002
a4_p0003
a4_p0004
a4_p0005
a4_p0006
a4_p0007
a4_p0008
a4_t0002
a4_t0003
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_2026_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
* 川合のプログラミング言語自作のためのテキスト第四版#0 -(by [[K]], 2026.05.27) ** (1) はじめに -プログラミング言語の自作のテキストを書いてからもう5年が経ちました。→[[a21_txt01]] -今の私なら、また全然違った切り口でプログラミング言語自作入門が書けるだろうと思って、試しに書いてみることにしました。 -今回は acl4 みたいなやり方で、つまり「ライブラリ自作駆動開発」でやってみようと思います。これは「わらしべ長者な開発」ともいえるもので、うまくいくととても楽しいので、ぜひこれを紹介しつつ、プログラミング言語づくりもしたいと思います。 -''[目次]'' --[[a26_txt03]] (acl4v2_000): はじめに, a_static, a_Version, a_class --[[a26_txt03_001]](acl4v2_001): デバッグレベル, malloc/free/realloc --(ここに目次を入れていく予定) ** (2) プログラミング言語の作り方として正しいのはどちらなのか? -前述の通り、私は以前「10日くらいでできる!プログラミング言語自作入門」を書いていました。今回は全く異なるやり方で言語を作っていきます。・・・そうなると、「じゃあどっちの作り方が正しいのか?」という疑問が出てくるのは当然だと思います。 -でもこれは「東京から大阪に行くときに、新幹線で行くのが正しいのか、飛行機で行くのが正しいのか、それともリニア新幹線の完成を待ってそれで行くのが正しいのか」みたいなもので、別にどれが正しいとか間違っているとかではないのです。 -私自身も、この2026年版のほうが後発だからこっちが優れている!と言いたい気持ちはあるものの、いやでもやっぱり客観的に見たら、一長一短というか、新幹線vs飛行機みたいなものでしょう。・・・ただまあ、そうですね、この2026年版のほうが意外性はあると思います。 -もし余裕があれば、両方の作り方の違いを眺めてみてください。なるほどねー、こっちではそうやるのかー、みたいな発見がきっとあります。 ** (3) 「ライブラリ自作駆動開発」とは何ですか? -最初に断っておきますが、この言葉は私が適当に考えた造語で、一般的に使われている言葉ではありません。だから辞書とかで調べてもきっとどこにも載ってないです(もし将来載るようになったら、びっくりです)。 -C言語は便利な関数を書き足していけば、どこまででも便利になる可能性を持っていると、私はずーっと前から思っていました。printf()やstrcmp()、sqrt()など標準関数は便利な機能を提供してくれています。もしそれらがなくて、そういうものも全部自作しなければいけないとしたら、それはとてもめんどくさいです(まあそれはそれでちょっと楽しいんですけどね)。 -C言語はC++やほかの言語に比べて機能が少なくて不便だと言われることが多いですが、それは第一には便利な関数が少ないからです。便利な関数をもっと増やせば、見違えるほど便利になるはずなんです。標準関数程度で満足せず、もっと便利になりそうな機能を追加していけば、すごいことになるんです。つまり言語が悪いわけではなく、ライブラリが悪いのです。・・・C言語は最初からライブラリによってパワーアップしていけるようになっていて、そういう拡張性のある言語だったはずなのに、あまりライブラリを使って言語の能力を底上げしていこう!みたいな流れにはならず、「はい、じゃあ新しい言語を作ったのでそちら行きましょう」が強調されて、ちょっと見捨てられ気味なのです。それはかわいそうじゃないですか。 -もちろんオブジェクト指向言語ではないので、クラスライブラリがきれいに書けないという問題はあります。でもそういうきれいさをあきらめれば、とにかくなんだってできるのです。ということで、これからどんどん作って、自分だけのスーパーC言語にしていきましょう。 -私の聞いたところによると、競技プログラミングが得意な人たちの多くはすでに自分のライブラリを持っていて、問題を早く解くために活用しているようです。そういう話を聞くとうまいなーと思います。 -みなさんは「わらしべ長者」という昔話をご存じでしょうか。・・・最初は一本のわらを持っていただけだったのに、物々交換を繰り返していくうちに、最後は家を手に入れてしまうというすごいお話です。 -Wikipediaにより詳しい説明があります → https://ja.wikipedia.org/wiki/%E3%82%8F%E3%82%89%E3%81%97%E3%81%B9%E9%95%B7%E8%80%85 -私はライブラリ自作駆動開発をわらしべ長者開発だと説明することもあります。なぜなら、最初は取るに足らない些細な関数を数個作るところから始まります。それができたら、その関数を使ってより高度な関数群を作ります。そしてそれもできたら、それらを使ってさらに高度な関数群を作っていくからです。・・・これを繰り返して、最後には(いや、それが最後というわけでもないのですが)、結構高度な関数をうまいこと作ってしまうのです。当初はまさかここまでできるなんて思いもしなかったのに。 -ここで作った関数群は、もちろん別の開発にも使えます。自作ライブラリは、自分の財産です。次の開発をするときには、わらしべ開発をやり直す必要はないので、最初から高度なことができます。こうして私は、以前の私よりもなんでもさっさと作れるようになっているわけです。私自身が成長したというよりは、ライブラリが成長したようなものですが、とにかく開発速度は上がっているのです。・・・ということで、私も読者の皆さんも、いっしょにスーパーなプログラマになろうじゃありませんか! ** (4) 著者について -私は「30日でできる!OS自作入門」の著者で、主にそこで有名になった感じです。 -学生の頃は機械語とかアセンブラが大好きという性格でした。・・・コンピュータは機械語で動いていて、だからこれを使いこなせばコンピュータのすべてが制御できると信じていたからです(まあ原理的には間違ってはいないです)。だからレジスタが何本あるとか、命令セットがどうなっているかとか、やたらと気にしていました。・・・でも実際は、コンパイラの最適化を超えるコードを手で書くのは結構大変で、だからまあ基本的にはコンパイラにお任せしてしまえばよく、そうなってしまうとレジスタとか命令セットとかは実はどうでもよかったりするわけなんですが、学生の頃の私は高価なコンパイラを買うとかはできなくて、16進数が大好きな青年でした(かつては無料で手に入るコンパイラなんてなかったですし、コンパイラの最適化能力も大したことはなく、手書きアセンブラは最強だったのです)。 -著者の性格としては、周囲の人が新しい技術に注目してどんどんと進んでいく中で、「いやでも、古い技術でもこうやればこれくらいはできるよ?」みたいなことを考えるのが好きで、だからこそ「なるほど、ここに関しては古い技術だけではマネできないな、きっとここが新しい技術の本質なんだな」なんて一人で納得したりしています。 -OS自作の人がなぜ言語自作をすることになったのか、たまに聞かれます。まあ確かに自明ではないですよね。・・・いくつか観点がありますが、ひとつには「そもそもなぜOS自作するのか」というところから考えるとわかりやすいと思います。まず私にとってOS自作は目的ではなく手段なのです。OSから全部作り直すことにすれば、私はソフトウェア世界のすべてを理想通りにできるはずなのです。そもそもOSを作るのってかなり大変なことです。それをわざわざやろうというのは、既存のOSに不満があるからです。それを全部直したいわけです。だけど、ある時気づいたんです。「プログラマ視点で見たら、別にOSの違いなんてあまり大きくなくて、言語の違いがすごく大きいのではないか?」と。だって同じ言語を使っていたら、OSがちょっと違っても、あまり差を感じずに済んだりしませんか?だったらまずは言語を作ったらいいじゃないか!と。 -それに、もし運よくとびっきり便利な言語ができてしまって生産性10倍とかになったら、それでOSを作ればいいじゃないですか。そうすれば10倍速くOSが完成するわけです。おお、それはいい。ぜひやろう。・・・この考え方は別に珍しいものではなく、そもそもC言語だってUNIXの開発効率を上げるために生まれてきた言語なのです。だからOS開発の人が言語開発に興味を持つのは、実は結構普通のことなのです。 -私はOS自作でちょっと名を上げたときに、先生方から「君がOSを作っていることは本当に素晴らしいと思うが、その技術を後輩たちに伝えることはできないだろうか?もしできなければ、君が死んでしまったらそれで終わりになってしまう」という指摘を受けました。それまでの私はとにかくいいものを自分が作ればいいとしか思っていなくて、この指摘はとても意外でした。しかし同時にとても納得しました。 -それ以降の私は、何かうまくできたと感じるたびに、その作品の成功だけではなく、やり方の継承について考えるようになりました。やり方を伝えることができれば、私一人でがんばるよりもずっと速く良いものが生まれてくるかもしれません。またこれからは、いろんな人のいろんなやり方が混ざっていいとこどりをして、「やり方」も大きく進歩していくかもしれません。そうなったらすごくすてきじゃないですか!・・・そうなったら私はなんとなく不老不死になった気分です(笑)。 -このテキストでは、何をどう作ったかという説明ももちろんしますが、それと同時に、何を考えてそうしているのかもできるだけ説明するようにしています。・・・実はそういう書き方には抵抗がありました。なぜなら私が自分の試行錯誤を書いてしまうと、読者はそれで満足してしまって試行錯誤を自分ではやらないようになってしまうかもしれないからです。また話も長くなってしまいます。しかし一方で、そういうことがきちんと書いてあるほうが読み物としては面白いです。それでまあ迷ったわけですが、とにかく今回はこのスタイルでやってみることにします。試さないであれこれ考えていてもしょうがないですよね。 -そもそもこんな「著者について」っていうのがなくてもライブラリ自作駆動開発は説明できるはずなんです。でも背景が分かったほうがより伝わるかもしれないじゃないですか。ということで、さっそくこのスタイルを実践しているというわけです。 ** (5) ライブラリの名称 -「acl4v2」とします。 -まず acl っていうのが何なのか、それを説明します。「a c-lang library」です。ある一つのC言語ライブラリ。theですらないです。その辺にあるライブラリのうちの一つです。取るに足らないものです。・・・これくらい控えめな名前にしておけば、名前負けすることはまずないですよね。 -4なのは私の中では acl シリーズの第四世代なので、それがそのままついています。・・・v2なのはマイナーチェンジを繰り返して、v1の次に作ったからv2です。 -まあつまり、全然かっこよくない名前です。いやだって、もう考えてもわからないので、ここに時間を使わないで中身で頑張ることにします。 -ちなみにイメージカラーは紅茶色です。・・・だったらこのテキストの色もそんな感じにしたらよさそうですが、wikiの設定を変えると今まで書いたやつも影響されそうですし、個別に色指定するほどのこだわりでもないので、まあこのままでいきます。 ** (6) ヘッダファイルを作るかどうか -従来のC言語の一般的なライブラリの作り方として「~.c」のほかに「~.h」というファイルを作って、インクルードは「~.h」だけやって、関数の実体を「~.c」において、ライブラリはライブラリだけでコンパイルして、アプリはアプリだけでコンパイルして、リンカでくっつける、という方法がとられてきました(分割コンパイル方式)。 -これの良いところはコンパイル時間が短くなることです。だから私もかつてはこの方法でライブラリを作ってきました。 -しかし今はPCの性能が非常に高くなっていて、わざわざ分割コンパイルでコンパイル速度を上げなくても、体感速度はほとんど変わらなくなってきました。・・・一方でコンパイラの視点に立てば、インクルードするのがヘッダファイルではなく実体であれば、関数呼び出し部分をインライン展開することだってできますし、どうやら3番目の引数jはいつも1で呼ばれるらしい、みたいなことに気づけばその性質を使って最適化をすることだってできます(現存するコンパイラでそこまでやってくれるかどうかは未確認ですが)。つまり大域的最適化ができるようになるのです。分割コンパイルではそういうことはやりたくてもやりようがないです。 -もちろん巨大なアプリとかを作る場合はそんなことを言っている余裕はないので、私も素直に分割コンパイルすると思いますが、私は普段30KBとか50KBとかのプログラムしか作らないので、分割しないコンパイルばかりしているわけです。 -分割コンパイルをしないならヘッダファイルは必要ありません。「~.c」の実体だけあれば十分です。 -一方でコンパイラはある関数定義を見たときに「この関数はどうやらこのプログラム内では一切呼ばれていないようだけど、これは分割コンパイル用のソースコードで他のソースコードから呼ばれるかもしれない」と考えます。だから全く使わない関数もご丁寧に実行ファイルに含めて出力してしまいます。・・・コード内で一度も使わない関数が居残るなんて、これは非常に邪魔です。アプリの実行ファイルが大きくなるだけです。だから使わない関数は消えてほしいです。 -そのためには関数の属性に static を付けます。そうすると、コンパイラは「この関数はこのソースコード内でしか使われない」と認識して、もし一度も使われなければ実行ファイルに含まないようにコード生成してくれます。すばらしいです。 -ということなので、関数宣言の時はどんどん static を付けましょう!・・・と言いたいところなのですが、そうすると今度は困ったことが起きます。もし巨大なアプリを作ることになって分割コンパイルをやりたくなった時に、ライブラリを分割コンパイルできなくなってしまうのです。がびーん。 static 指定が邪魔なんです。 -ということで、何ができれば理想的かというと、 static を一斉につけたり消したりできればいいわけです。 -それでこんな書き方になりました。 #if (!defined(MyStatic)) #define MyStatic static #endif ... MyStatic int libFunc1(int a, int b, int c) { .... } -これで MyStatic というシンボルを空にしてdefineしておけば分割コンパイル対応ソースコードになりますし、未定義のままなら勝手に static 属性がつくようになります。やったね! ** (7) 関数名をどうするか -C言語の標準ライブラリは、すごく自己中心的で、sinとかsqrtとかmallocとかfreeとか、好き勝手に関数名を使っている印象があります。これは標準関数ではないですが、あるときmaxとかminまでライブラリ側で定義していて使っていて、とても迷惑したことがありました。 -私の考えではわかりやすい名前・使いやすい名前はライブラリ側は避けるようにして、アプリプログラマに使わせてあげるべきです。というかアプリ開発者の思い通りに変数名や関数名を使わせてくれないライブラリは、ちっとも便利ではありません! -そういう意味では、先の(6)に書いた「MyStatic」はいい名前ではありません。これくらいの名前なら、アプリ側で無意識に使う可能性が十分にあります。 -では関数名はどうしたらいいでしょうか。私はかつてはライブラリ名を頭につけるルールを採用していました。このルールだと「acl4v2_static」になります。・・・この命名規則はアプリ開発時に衝突事故が起きる可能性がほぼゼロなので、その点は最高にすばらしいですが、関数名がとにかく長くて、うっとうしいのです。便利な関数はとてもよく使います。よく使う関数に毎回 acl4v2_ ってつけると、けっこう目障りなのです。 -ということで、私が到達した最終形態を見せます。 #if (!defined(a_Version)) #define a_Version 9999 #endif #if (a_Version > 0) #define static_ a_static ... #define VecChr a_VecChr #define Set0 a_Set0 ... #endif .... #if (!defined(a_static)) #define a_static static #endif ... -まず最初の a_Version に関する記述は全部無視してください。それはあとで説明します。 -すると私は MyStatic でも acl4v2_static でもなく、 a_static と書いていることが分かります。そう、私はacl4v2ライブラリの関数は「a_」で書き始めることにしたのです。短いので衝突リスクは多少残りますが、まあ覚えやすいのでこれで許してください、という感じです。 -そして a_Version です。これが何のためにあるかですが、デフォルトではこのマクロは未定義なので9999がdefineされて、その後の条件コンパイルが有効になります。そうすると、a_を付けずに書き始めても勝手にa_を補ってくれるようになります。・・・つまりa_は省略できるのです。これなら全くうっとうしくないです! -そしてもし省略形がアプリ側の変数名・関数名と衝突が起きて困るなら、 a_Version に 0 をdefineしてからインクルードすればいいわけです。そうすれば省略形は使えなくなるので衝突は起きません。 -さらに考えられるケースとして、ある時点でacl4v2ライブラリを活用して何かプログラムを作ったとします。その後acl4v2ライブラリがバージョンアップして、関数が追加されたとします。このとき追加された関数と同じ名前の関数や変数名がアプリ側にあると、とても困ったことになります。なぜなら衝突を避けようとして a_Version に 0 をdefineすれば、それまで省略形で書いてきた部分の書き直しを迫られます。これはめんどくさいです。でも何もしなければ衝突は解消できません。 -そんな時は、a_Versionを1とか2とかにするのです。acl4v2ライブラリでは、バージョンアップして関数を追加するときは、 #if (a_Version > 1) 追加された関数の省略形の定義 #endif -という形で書かれるので、省略形をどこまで有効にするかを選べるのです。だから必ず名前の衝突を避けることができます。 ** (8) acl4v2.c #include "acl4v2_000.c" #include "acl4v2_001.c" #include "acl4v2_002.c" #include "acl4v2_003.c" #include "acl4v2_004.c" #include "acl4v2_005.c" ... ** (9) acl4v2_000.c #if (!defined(a_Version)) #define a_Version 9999 #endif #if (a_Version >= 1) #define static_ a_static #define class_ a_class #endif #if (!defined(a_static)) #define a_static static #endif #include <ctype.h> #include <errno.h> #include <float.h> #include <inttypes.h> #include <limits.h> #include <locale.h> #include <math.h> #include <setjmp.h> #include <signal.h> #include <stdarg.h> #include <stddef.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #define a_class(c) typedef struct c ## _ c; struct c ## _ ** (10) 追加の説明 -[Q]なぜincludeがたくさんあるの? -[A]それはacl4v2.cをインクルードすれば標準関数が一通り使えるという状態にしておきたかったからです。そのほうが便利ですよね? -[Q]a_class()というマクロは何ですか? -[A]普通にstructを使って構造体宣言をすると、Abcと書くだけでは型を意味すると思ってもらえずエラーになります。 struct Abc { int a, b, c; }; Abc abc; // エラーになる. struct Abc abc; // これなら許してくれる. -でもそれは不便です。C++のようにstructを省略できるようになりたいです。それにはtypedefを使うといいのですが、それを全部まとめてやってくれるマクロです。だから以下のように書けます。 class_(Abc) { int a, b, c; }; Abc abc; // エラーにならない. -ちなみになぜエラーにならなくなるのかというと、結果的に以下のように展開されるからです。 typdef struct Abc_ Abc; struct Abc_ { int a, b, c; }; ** (11) あとがき? -今回はたくさん書いたのに、コード本体(9)は30行しか進みませんでした。 -まあそれだけ丁寧に説明したってことでいいのかな? -続きはこちら → [[a26_txt03_001]] * こめんと欄 #comment
タイムスタンプを変更しない
* 川合のプログラミング言語自作のためのテキスト第四版#0 -(by [[K]], 2026.05.27) ** (1) はじめに -プログラミング言語の自作のテキストを書いてからもう5年が経ちました。→[[a21_txt01]] -今の私なら、また全然違った切り口でプログラミング言語自作入門が書けるだろうと思って、試しに書いてみることにしました。 -今回は acl4 みたいなやり方で、つまり「ライブラリ自作駆動開発」でやってみようと思います。これは「わらしべ長者な開発」ともいえるもので、うまくいくととても楽しいので、ぜひこれを紹介しつつ、プログラミング言語づくりもしたいと思います。 -''[目次]'' --[[a26_txt03]] (acl4v2_000): はじめに, a_static, a_Version, a_class --[[a26_txt03_001]](acl4v2_001): デバッグレベル, malloc/free/realloc --(ここに目次を入れていく予定) ** (2) プログラミング言語の作り方として正しいのはどちらなのか? -前述の通り、私は以前「10日くらいでできる!プログラミング言語自作入門」を書いていました。今回は全く異なるやり方で言語を作っていきます。・・・そうなると、「じゃあどっちの作り方が正しいのか?」という疑問が出てくるのは当然だと思います。 -でもこれは「東京から大阪に行くときに、新幹線で行くのが正しいのか、飛行機で行くのが正しいのか、それともリニア新幹線の完成を待ってそれで行くのが正しいのか」みたいなもので、別にどれが正しいとか間違っているとかではないのです。 -私自身も、この2026年版のほうが後発だからこっちが優れている!と言いたい気持ちはあるものの、いやでもやっぱり客観的に見たら、一長一短というか、新幹線vs飛行機みたいなものでしょう。・・・ただまあ、そうですね、この2026年版のほうが意外性はあると思います。 -もし余裕があれば、両方の作り方の違いを眺めてみてください。なるほどねー、こっちではそうやるのかー、みたいな発見がきっとあります。 ** (3) 「ライブラリ自作駆動開発」とは何ですか? -最初に断っておきますが、この言葉は私が適当に考えた造語で、一般的に使われている言葉ではありません。だから辞書とかで調べてもきっとどこにも載ってないです(もし将来載るようになったら、びっくりです)。 -C言語は便利な関数を書き足していけば、どこまででも便利になる可能性を持っていると、私はずーっと前から思っていました。printf()やstrcmp()、sqrt()など標準関数は便利な機能を提供してくれています。もしそれらがなくて、そういうものも全部自作しなければいけないとしたら、それはとてもめんどくさいです(まあそれはそれでちょっと楽しいんですけどね)。 -C言語はC++やほかの言語に比べて機能が少なくて不便だと言われることが多いですが、それは第一には便利な関数が少ないからです。便利な関数をもっと増やせば、見違えるほど便利になるはずなんです。標準関数程度で満足せず、もっと便利になりそうな機能を追加していけば、すごいことになるんです。つまり言語が悪いわけではなく、ライブラリが悪いのです。・・・C言語は最初からライブラリによってパワーアップしていけるようになっていて、そういう拡張性のある言語だったはずなのに、あまりライブラリを使って言語の能力を底上げしていこう!みたいな流れにはならず、「はい、じゃあ新しい言語を作ったのでそちら行きましょう」が強調されて、ちょっと見捨てられ気味なのです。それはかわいそうじゃないですか。 -もちろんオブジェクト指向言語ではないので、クラスライブラリがきれいに書けないという問題はあります。でもそういうきれいさをあきらめれば、とにかくなんだってできるのです。ということで、これからどんどん作って、自分だけのスーパーC言語にしていきましょう。 -私の聞いたところによると、競技プログラミングが得意な人たちの多くはすでに自分のライブラリを持っていて、問題を早く解くために活用しているようです。そういう話を聞くとうまいなーと思います。 -みなさんは「わらしべ長者」という昔話をご存じでしょうか。・・・最初は一本のわらを持っていただけだったのに、物々交換を繰り返していくうちに、最後は家を手に入れてしまうというすごいお話です。 -Wikipediaにより詳しい説明があります → https://ja.wikipedia.org/wiki/%E3%82%8F%E3%82%89%E3%81%97%E3%81%B9%E9%95%B7%E8%80%85 -私はライブラリ自作駆動開発をわらしべ長者開発だと説明することもあります。なぜなら、最初は取るに足らない些細な関数を数個作るところから始まります。それができたら、その関数を使ってより高度な関数群を作ります。そしてそれもできたら、それらを使ってさらに高度な関数群を作っていくからです。・・・これを繰り返して、最後には(いや、それが最後というわけでもないのですが)、結構高度な関数をうまいこと作ってしまうのです。当初はまさかここまでできるなんて思いもしなかったのに。 -ここで作った関数群は、もちろん別の開発にも使えます。自作ライブラリは、自分の財産です。次の開発をするときには、わらしべ開発をやり直す必要はないので、最初から高度なことができます。こうして私は、以前の私よりもなんでもさっさと作れるようになっているわけです。私自身が成長したというよりは、ライブラリが成長したようなものですが、とにかく開発速度は上がっているのです。・・・ということで、私も読者の皆さんも、いっしょにスーパーなプログラマになろうじゃありませんか! ** (4) 著者について -私は「30日でできる!OS自作入門」の著者で、主にそこで有名になった感じです。 -学生の頃は機械語とかアセンブラが大好きという性格でした。・・・コンピュータは機械語で動いていて、だからこれを使いこなせばコンピュータのすべてが制御できると信じていたからです(まあ原理的には間違ってはいないです)。だからレジスタが何本あるとか、命令セットがどうなっているかとか、やたらと気にしていました。・・・でも実際は、コンパイラの最適化を超えるコードを手で書くのは結構大変で、だからまあ基本的にはコンパイラにお任せしてしまえばよく、そうなってしまうとレジスタとか命令セットとかは実はどうでもよかったりするわけなんですが、学生の頃の私は高価なコンパイラを買うとかはできなくて、16進数が大好きな青年でした(かつては無料で手に入るコンパイラなんてなかったですし、コンパイラの最適化能力も大したことはなく、手書きアセンブラは最強だったのです)。 -著者の性格としては、周囲の人が新しい技術に注目してどんどんと進んでいく中で、「いやでも、古い技術でもこうやればこれくらいはできるよ?」みたいなことを考えるのが好きで、だからこそ「なるほど、ここに関しては古い技術だけではマネできないな、きっとここが新しい技術の本質なんだな」なんて一人で納得したりしています。 -OS自作の人がなぜ言語自作をすることになったのか、たまに聞かれます。まあ確かに自明ではないですよね。・・・いくつか観点がありますが、ひとつには「そもそもなぜOS自作するのか」というところから考えるとわかりやすいと思います。まず私にとってOS自作は目的ではなく手段なのです。OSから全部作り直すことにすれば、私はソフトウェア世界のすべてを理想通りにできるはずなのです。そもそもOSを作るのってかなり大変なことです。それをわざわざやろうというのは、既存のOSに不満があるからです。それを全部直したいわけです。だけど、ある時気づいたんです。「プログラマ視点で見たら、別にOSの違いなんてあまり大きくなくて、言語の違いがすごく大きいのではないか?」と。だって同じ言語を使っていたら、OSがちょっと違っても、あまり差を感じずに済んだりしませんか?だったらまずは言語を作ったらいいじゃないか!と。 -それに、もし運よくとびっきり便利な言語ができてしまって生産性10倍とかになったら、それでOSを作ればいいじゃないですか。そうすれば10倍速くOSが完成するわけです。おお、それはいい。ぜひやろう。・・・この考え方は別に珍しいものではなく、そもそもC言語だってUNIXの開発効率を上げるために生まれてきた言語なのです。だからOS開発の人が言語開発に興味を持つのは、実は結構普通のことなのです。 -私はOS自作でちょっと名を上げたときに、先生方から「君がOSを作っていることは本当に素晴らしいと思うが、その技術を後輩たちに伝えることはできないだろうか?もしできなければ、君が死んでしまったらそれで終わりになってしまう」という指摘を受けました。それまでの私はとにかくいいものを自分が作ればいいとしか思っていなくて、この指摘はとても意外でした。しかし同時にとても納得しました。 -それ以降の私は、何かうまくできたと感じるたびに、その作品の成功だけではなく、やり方の継承について考えるようになりました。やり方を伝えることができれば、私一人でがんばるよりもずっと速く良いものが生まれてくるかもしれません。またこれからは、いろんな人のいろんなやり方が混ざっていいとこどりをして、「やり方」も大きく進歩していくかもしれません。そうなったらすごくすてきじゃないですか!・・・そうなったら私はなんとなく不老不死になった気分です(笑)。 -このテキストでは、何をどう作ったかという説明ももちろんしますが、それと同時に、何を考えてそうしているのかもできるだけ説明するようにしています。・・・実はそういう書き方には抵抗がありました。なぜなら私が自分の試行錯誤を書いてしまうと、読者はそれで満足してしまって試行錯誤を自分ではやらないようになってしまうかもしれないからです。また話も長くなってしまいます。しかし一方で、そういうことがきちんと書いてあるほうが読み物としては面白いです。それでまあ迷ったわけですが、とにかく今回はこのスタイルでやってみることにします。試さないであれこれ考えていてもしょうがないですよね。 -そもそもこんな「著者について」っていうのがなくてもライブラリ自作駆動開発は説明できるはずなんです。でも背景が分かったほうがより伝わるかもしれないじゃないですか。ということで、さっそくこのスタイルを実践しているというわけです。 ** (5) ライブラリの名称 -「acl4v2」とします。 -まず acl っていうのが何なのか、それを説明します。「a c-lang library」です。ある一つのC言語ライブラリ。theですらないです。その辺にあるライブラリのうちの一つです。取るに足らないものです。・・・これくらい控えめな名前にしておけば、名前負けすることはまずないですよね。 -4なのは私の中では acl シリーズの第四世代なので、それがそのままついています。・・・v2なのはマイナーチェンジを繰り返して、v1の次に作ったからv2です。 -まあつまり、全然かっこよくない名前です。いやだって、もう考えてもわからないので、ここに時間を使わないで中身で頑張ることにします。 -ちなみにイメージカラーは紅茶色です。・・・だったらこのテキストの色もそんな感じにしたらよさそうですが、wikiの設定を変えると今まで書いたやつも影響されそうですし、個別に色指定するほどのこだわりでもないので、まあこのままでいきます。 ** (6) ヘッダファイルを作るかどうか -従来のC言語の一般的なライブラリの作り方として「~.c」のほかに「~.h」というファイルを作って、インクルードは「~.h」だけやって、関数の実体を「~.c」において、ライブラリはライブラリだけでコンパイルして、アプリはアプリだけでコンパイルして、リンカでくっつける、という方法がとられてきました(分割コンパイル方式)。 -これの良いところはコンパイル時間が短くなることです。だから私もかつてはこの方法でライブラリを作ってきました。 -しかし今はPCの性能が非常に高くなっていて、わざわざ分割コンパイルでコンパイル速度を上げなくても、体感速度はほとんど変わらなくなってきました。・・・一方でコンパイラの視点に立てば、インクルードするのがヘッダファイルではなく実体であれば、関数呼び出し部分をインライン展開することだってできますし、どうやら3番目の引数jはいつも1で呼ばれるらしい、みたいなことに気づけばその性質を使って最適化をすることだってできます(現存するコンパイラでそこまでやってくれるかどうかは未確認ですが)。つまり大域的最適化ができるようになるのです。分割コンパイルではそういうことはやりたくてもやりようがないです。 -もちろん巨大なアプリとかを作る場合はそんなことを言っている余裕はないので、私も素直に分割コンパイルすると思いますが、私は普段30KBとか50KBとかのプログラムしか作らないので、分割しないコンパイルばかりしているわけです。 -分割コンパイルをしないならヘッダファイルは必要ありません。「~.c」の実体だけあれば十分です。 -一方でコンパイラはある関数定義を見たときに「この関数はどうやらこのプログラム内では一切呼ばれていないようだけど、これは分割コンパイル用のソースコードで他のソースコードから呼ばれるかもしれない」と考えます。だから全く使わない関数もご丁寧に実行ファイルに含めて出力してしまいます。・・・コード内で一度も使わない関数が居残るなんて、これは非常に邪魔です。アプリの実行ファイルが大きくなるだけです。だから使わない関数は消えてほしいです。 -そのためには関数の属性に static を付けます。そうすると、コンパイラは「この関数はこのソースコード内でしか使われない」と認識して、もし一度も使われなければ実行ファイルに含まないようにコード生成してくれます。すばらしいです。 -ということなので、関数宣言の時はどんどん static を付けましょう!・・・と言いたいところなのですが、そうすると今度は困ったことが起きます。もし巨大なアプリを作ることになって分割コンパイルをやりたくなった時に、ライブラリを分割コンパイルできなくなってしまうのです。がびーん。 static 指定が邪魔なんです。 -ということで、何ができれば理想的かというと、 static を一斉につけたり消したりできればいいわけです。 -それでこんな書き方になりました。 #if (!defined(MyStatic)) #define MyStatic static #endif ... MyStatic int libFunc1(int a, int b, int c) { .... } -これで MyStatic というシンボルを空にしてdefineしておけば分割コンパイル対応ソースコードになりますし、未定義のままなら勝手に static 属性がつくようになります。やったね! ** (7) 関数名をどうするか -C言語の標準ライブラリは、すごく自己中心的で、sinとかsqrtとかmallocとかfreeとか、好き勝手に関数名を使っている印象があります。これは標準関数ではないですが、あるときmaxとかminまでライブラリ側で定義していて使っていて、とても迷惑したことがありました。 -私の考えではわかりやすい名前・使いやすい名前はライブラリ側は避けるようにして、アプリプログラマに使わせてあげるべきです。というかアプリ開発者の思い通りに変数名や関数名を使わせてくれないライブラリは、ちっとも便利ではありません! -そういう意味では、先の(6)に書いた「MyStatic」はいい名前ではありません。これくらいの名前なら、アプリ側で無意識に使う可能性が十分にあります。 -では関数名はどうしたらいいでしょうか。私はかつてはライブラリ名を頭につけるルールを採用していました。このルールだと「acl4v2_static」になります。・・・この命名規則はアプリ開発時に衝突事故が起きる可能性がほぼゼロなので、その点は最高にすばらしいですが、関数名がとにかく長くて、うっとうしいのです。便利な関数はとてもよく使います。よく使う関数に毎回 acl4v2_ ってつけると、けっこう目障りなのです。 -ということで、私が到達した最終形態を見せます。 #if (!defined(a_Version)) #define a_Version 9999 #endif #if (a_Version > 0) #define static_ a_static ... #define VecChr a_VecChr #define Set0 a_Set0 ... #endif .... #if (!defined(a_static)) #define a_static static #endif ... -まず最初の a_Version に関する記述は全部無視してください。それはあとで説明します。 -すると私は MyStatic でも acl4v2_static でもなく、 a_static と書いていることが分かります。そう、私はacl4v2ライブラリの関数は「a_」で書き始めることにしたのです。短いので衝突リスクは多少残りますが、まあ覚えやすいのでこれで許してください、という感じです。 -そして a_Version です。これが何のためにあるかですが、デフォルトではこのマクロは未定義なので9999がdefineされて、その後の条件コンパイルが有効になります。そうすると、a_を付けずに書き始めても勝手にa_を補ってくれるようになります。・・・つまりa_は省略できるのです。これなら全くうっとうしくないです! -そしてもし省略形がアプリ側の変数名・関数名と衝突が起きて困るなら、 a_Version に 0 をdefineしてからインクルードすればいいわけです。そうすれば省略形は使えなくなるので衝突は起きません。 -さらに考えられるケースとして、ある時点でacl4v2ライブラリを活用して何かプログラムを作ったとします。その後acl4v2ライブラリがバージョンアップして、関数が追加されたとします。このとき追加された関数と同じ名前の関数や変数名がアプリ側にあると、とても困ったことになります。なぜなら衝突を避けようとして a_Version に 0 をdefineすれば、それまで省略形で書いてきた部分の書き直しを迫られます。これはめんどくさいです。でも何もしなければ衝突は解消できません。 -そんな時は、a_Versionを1とか2とかにするのです。acl4v2ライブラリでは、バージョンアップして関数を追加するときは、 #if (a_Version > 1) 追加された関数の省略形の定義 #endif -という形で書かれるので、省略形をどこまで有効にするかを選べるのです。だから必ず名前の衝突を避けることができます。 ** (8) acl4v2.c #include "acl4v2_000.c" #include "acl4v2_001.c" #include "acl4v2_002.c" #include "acl4v2_003.c" #include "acl4v2_004.c" #include "acl4v2_005.c" ... ** (9) acl4v2_000.c #if (!defined(a_Version)) #define a_Version 9999 #endif #if (a_Version >= 1) #define static_ a_static #define class_ a_class #endif #if (!defined(a_static)) #define a_static static #endif #include <ctype.h> #include <errno.h> #include <float.h> #include <inttypes.h> #include <limits.h> #include <locale.h> #include <math.h> #include <setjmp.h> #include <signal.h> #include <stdarg.h> #include <stddef.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #define a_class(c) typedef struct c ## _ c; struct c ## _ ** (10) 追加の説明 -[Q]なぜincludeがたくさんあるの? -[A]それはacl4v2.cをインクルードすれば標準関数が一通り使えるという状態にしておきたかったからです。そのほうが便利ですよね? -[Q]a_class()というマクロは何ですか? -[A]普通にstructを使って構造体宣言をすると、Abcと書くだけでは型を意味すると思ってもらえずエラーになります。 struct Abc { int a, b, c; }; Abc abc; // エラーになる. struct Abc abc; // これなら許してくれる. -でもそれは不便です。C++のようにstructを省略できるようになりたいです。それにはtypedefを使うといいのですが、それを全部まとめてやってくれるマクロです。だから以下のように書けます。 class_(Abc) { int a, b, c; }; Abc abc; // エラーにならない. -ちなみになぜエラーにならなくなるのかというと、結果的に以下のように展開されるからです。 typdef struct Abc_ Abc; struct Abc_ { int a, b, c; }; ** (11) あとがき? -今回はたくさん書いたのに、コード本体(9)は30行しか進みませんでした。 -まあそれだけ丁寧に説明したってことでいいのかな? -続きはこちら → [[a26_txt03_001]] * こめんと欄 #comment
テキスト整形のルールを表示する