kuincppでcursesをラップする
これはプログラミング言語Kuin(C++版)のexcode文を使って悪事(若干非自明なプチテクニック)をはたらきつつ、ついでに有名なcursesライブラリをラップするといった記事です
対象読者はよくわかりません
よろしくお願いします
の前に
本記事で扱うコンパイラは執筆時点でも「古いバージョン」とされているkuin_2021_08_17_src_jaです 新しいバージョンがまだ公開されていないためです 未来にお住まいのみなさんは注意してください
知識: トランスパイラとexcodeの仕様
cursesをラップするのに悪事が必要な理由はKuin処理系の仕様にあります
C++版のKuin(kuincppと勝手に呼んでいる)処理系として公式が出しているものはKuin->C++トランスパイラなんですが、Kuinコード内にトランスパイル後のC++コードを直接書けるexcode文というのが存在していて、excode "ここにC++コードを書く"という感じで書けます
code: excode_example.kn
func main()
excode("//このように")
excode("int64_t a = 42;")
var b: int :: 57
end func
とりあえずこれをトランスパイルしてみましょう
code: excode_example.cpp
...
// main
static void k_g(){
int64_t k_al; // b
//このようにint64_t a = 42;(k_al) = (57LL);
}
...
次のことが分かります
末尾に勝手に改行を追加したりしない
(excode関係ないけど)関数名や変数名が置換される
順番が入れ替わっているように見えるのは宣言だけ上にまとめているんでしょうか うにょーん
また、関数の外にexcodeを置けないみたいです
コンパイルエラー(トランスパイルエラー?)になります
code: *excode_outside_function.kn
excode("//ここには書けない\n")
func main()
end func
ちょっとexcode関係ないですが、複数の関数を書いた場合どのようなC++コードになるかも確認しておきます
code: multiple_functions.kn
func main()
do @b(42)
do @a()
end func
func a()
do cui@print("I am function a\n")
end func
func b(i: int)
do cui@print("I am function b: i = \{i}\n")
end func
func c()
do cui@print("I am function c\n")
end func
関数a、b、cの順に定義してb、aの順に呼んでみました
cは呼びません
code: multiple_functions.cpp
...
// main
static void k_g(){
(k_am)((42LL));
(k_an)();
}
...
// b
static void k_am(int64_t k_do /*i*/){
(k_dp)((((newArrayBin_<char16_t>(21,"\x49\x00\x20\x00\x61\x00\x6D\x00\x20\x00\x66\x00\x75\x00\x6E\x00\x63\x00\x74\x00\x69\x00\x6F\x00\x6E\x00\x20\x00\x62\x00\x3A\x00\x20\x00\x69\x00\x20\x00\x3D\x00\x20\x00"))->Cat(k_dq<int64_t>((k_do), (nullptr))))->Cat(newArrayBin_<char16_t>(1,"\x0A\x00"))));
}
// a
static void k_an(){
(k_dp)((newArrayBin_<char16_t>(16,"\x49\x00\x20\x00\x61\x00\x6D\x00\x20\x00\x66\x00\x75\x00\x6E\x00\x63\x00\x74\x00\x69\x00\x6F\x00\x6E\x00\x20\x00\x61\x00\x0A\x00")));
}
...
main、b、aの順でした
呼ばれた関数だけ呼ばれた順にトランスパイルされるんですね なるほど〜
問題の整理
上に太字で書いた仕様のうち、ラッパを作る上で障壁になりそうなもの2つを斜字体にしておきました
次に再掲します
関数の外にexcodeを置けない
includeをどうするか?
関数名や変数名が置換される
excode内から外の変数にアクセスできない
これらを解決することが本記事の趣旨です 前置きが長い!
関数の外にexcodeを置く
これはわりと簡単で、excode("}")などとして関数を終わらせればいいです
end funcに対応する}が余るので、excode("//")でコメントアウトします
勝手に改行を追加したりしない仕様を生かすわけです
あとはこの2つのexcodeの間(Kuinでは関数内だが、C++では関数外)でこの世の悪を極めましょう
code: akuji1.kn
func hoge()
excode("}\n")
excode("#include <akuji.h>\n")
excode("//")
end func
code: akuji1.cpp
...
// hoge
static void k_al(){
}
//}
...
変数名を指定して代入する
トランスパイル時に変数名が(恐らく予測不可能な名前に)置換されるせいで、例えば関数の引数にもアクセスできません
excode内の文字列に式を埋め込もうとするとコンパイルエラーになってしまいます
code: *format_string_in_excode.kn
func fuga(i: int)
excode("int64_t i2 = \{i};\n")
end func
どうしましょう うーんうーん
せや
code: akuji2.kn
func fuga(i: int)
excode("int64_t i2 = ")
do i :: i
end func
code: akuji2.cpp
...
// fuga
static void k_al(int64_t k_dm /*i*/){
int64_t i2 = (k_dm) = (k_dm);
}
...
無意味な代入を書くことで指定した名前の変数の値をC++ランドに持ち込むことができました
これで悪事の幅も広がりますね♪
ちなみにdo i :: iではなくdo iと書くとコンパイルエラーになります
code: *akuji2_wrong.kn
func fuga(i: int)
excode("int64_t i2 = ")
do i
end func
cursesをラップする
上述の解決策を使ってラップをします YO
作戦としては、初期化関数と称して中身のない関数を最初に呼んでもらうことにして、この中でincludeをします
あとはひたすらcursesの関数をKuinの関数として呼べるようにしていきます
code: curses.kn
+func init()
excode("}\n")
excode("#include <curses.h>\n")
excode("//")
end func
+func initscr()
excode("initscr();\n")
end func
...
+func mvaddstr(y: int, x: int, str: []char)
excode("int64_t y = ")
do y :: y
excode("int64_t x = ")
do x :: x
excode("type_(Array_<char16_t>) str = ")
do str :: str
excode("std::u16string s_ = str->B;\n")
excode("const std::string&t_ = utf16ToUtf8_(s_);\n")
excode("mvaddstr(y, x, t_.c_str());\n")
end func
...
initを最初に呼んでもらえば#include <curses.h>がinitscr等の関数よりも上に配置されるのでちゃんと動くはずです
curses.knはもうちょっと対応関数を増やしたら公開しようかな めんどくさいのでとりあえず必要最低限の関数だけ包んでおきます
☆getmaxyxみたいな値をセットしてくるやつの対処法が分からない もちもち
動作確認
気付いたら前日になっていた
悪事がどうのとかいうノリがもどって来ず センスの部分だけで何とかする必要がある(うるさ!)
作ったcurses.knを使ってみます
code: sample_program.kn
func main()
do \curses@init()
do \curses@initscr()
do \curses@start_color()
do \curses@init_pair(2, %color_yellow, %color_black)
do \curses@init_pair(3, %color_green, %color_black)
do \curses@attron(\curses@COLOR_PAIR(2))
do \curses@mvaddstr( 3, 0, " ✩")
do \curses@attroff(\curses@COLOR_PAIR(2))
do \curses@attron(\curses@COLOR_PAIR(3))
do \curses@mvaddstr( 4, 0, " / \\")
do \curses@mvaddstr( 5, 0, " / \\")
do \curses@mvaddstr( 6, 0, " / \\")
do \curses@mvaddstr( 7, 0, " -- --")
do \curses@mvaddstr( 8, 0, " / \\")
do \curses@mvaddstr( 9, 0, " / \\")
do \curses@mvaddstr(10, 0, " / \\")
do \curses@mvaddstr(11, 0, " / \\")
do \curses@mvaddstr(12, 0, " -- --")
do \curses@mvaddstr(13, 0, " / \\")
do \curses@mvaddstr(14, 0, " / \\")
do \curses@mvaddstr(15, 0, " / \\")
do \curses@mvaddstr(16, 0, " / \\")
do \curses@mvaddstr(17, 0, " / \\")
do \curses@mvaddstr(18, 0, " =+=+=+=+=+=+=+=+=+=+=+=+=+=+=")
do \curses@mvaddstr(19, 0, " | |")
do \curses@mvaddstr(20, 0, " | | ||")
do \curses@mvaddstr(21, 0, " [ 🍎")
do \curses@mvaddstr(22, 0, " ] ")
do \curses@mvaddstr(23, 0, " [ ")
do \curses@mvaddstr(24, 0, " ] ")
do \curses@attroff(\curses@COLOR_PAIR(3))
do \curses@mvaddstr( 9, 40, "MERRY GRAV-MASS")
do \curses@mvaddstr(10, 40, " && ")
do \curses@mvaddstr(11, 40, " HAPPY CODING")
do \curses@refresh()
do \curses@getch()
do \curses@endwin()
end func
先に全部分かってしまうのがAAの悪いところですね
実行してみます
cursesのリンクを忘れずに…
https://scrapbox.io/files/69303f8a7bc6e1684aa64dd2.png
かっこいい
おわりに
ここまで読んでくれてありがとうございました〜