おもちゃバコ

中身スカスカ♡

NeoVimを触ってみた

こんにちは

Neovimに入門したときの備忘録です。
(vimについて調べ物をすると目にする機会が多いので気になってた。)


Neovimとは

Neovimの公式を見ればわかりますが簡単にまとめました。
間違えているかもしれないので詳細を知りたい方は本家へ。

github.com

参考

有名なvim


環境

  • Windows11
  • cmd.exe
  • winget v1.4.10173

今回はPythonやGitなどのを利用するのはなるべく避けるようにしました。
依存ソフトウェアは少ないほうが好き。

(特にPythonは色々な環境で使われているので, 特殊な設定がされていると上手く動作しないかもしれないのが怖かった)

インストール

Windows10以降はwingetでインストール出来るようです(Installing Neovim)

powershellかcmdを開いてwingetする。
winget install Neovim.Neovim

Windows Terminalでコマンドプロンプトを開いてwingetしたのですが, インストール完了後に一度Windows Terminalを閉じないとnvimコマンドが使用できませんでした。
(おま環っぽい?)

Windows10からパッケージマネージャが標準でインストールされていることを初めて知りました。
インストールも楽でめちゃくちゃ便利だね。

起動

powershellとかcmdにnvimと打てば起動する。

初期画面が nvim 専用!!!

噂のターミナルモードもいい感じだったし,なんかマウスも使えた。


init.vim

Installing Neovimによると, init.vimがNeovimにおける.vimrc的なものらしい。

init.vimの配置場所は%userprofile%\AppData\Local\nvim\init.vimみたいだけど, nvimフォルダは存在しないので新規作成した。

シンボリックリンクがまともに機能する環境なら,.vimrcをリンクさせるだけで良いはず。
っと思ったけど,Windows用のシンボリックコマンドmklinkでは読み込みできませんでした...
よくわかんない。

とりあえず.vimrcにif文でNeovim専用処理を記述した。

if has('nvim')
    " Neovim専用処理
endif

このinit.vimは下に記述するプラグインも含めた完全体。


プラグイン

このままでは本家vimと変わらないのでプラグインを入れてみる。

プラグインマネージャ

プラグインマネージャにも色々ありますが, 軽量で依存ソフトが少ないvim-jetpackを導入しました。

vim-jetpackについての詳細は開発者さんの解説へ。
zenn.dev

インストール詳細は公式のgithubへ。

1.ダウンロード
USERPROFILEは各自で変えてね。
curl -fLo %USERPROFILE%\AppData\Local\nvim-data\site\pack\jetpack\opt\vim-jetpack\plugin\jetpack.vim --create-dirs https://raw.githubusercontent.com/tani/vim-jetpack/master/plugin/jetpack.vim

gitが無い環境の人は下記を記述してcurlを使おう。
let g:jetpack_download_method = 'curl'

2.init.vim
とりあえずBootstrapだけ追加。

packadd vim-jetpack
call jetpack#begin()
Jetpack 'tani/vim-jetpack', {'opt': 1} "bootstrap
call jetpack#end()

3.Neovim
Neovimを起動して:JetpackSync

プラグイン

Neovim用のプラグインを導入してみた。
依存が少ないものを選んだつもり。

fern.vim

ファイラーの1つ。
github.com

Jetpack 'lambdalisue/fern.vim'をinit.vimに追加して:JetpackSyncする。

:Fern .で呼び出し。
詳細はFernウィンドウ内でaキー入力後にhelpを入力。
隠しファイルはデフォルトでは見えないのでinit.vimに追記が必要。

init.vimに下記を記述しました。

let g:fern#default_hidden=1 " Show hidden files.
noremap <F12> :Fern . -reveal=% -drawer -toggle -width=30<CR>

アイコン表示とかはフォントの設定が必要そうなので今回はパス。

vim-airline

vimのステータスバーをリッチにするプラグインです。
github.com

Jetpack 'vim-airline/vim-airline'をinit.vimに追加して:JetpackSyncする。
インストール後は再起動すれば自動で適用されていますが,init.vimにも設定を追記しておくのが良いかも。

ついでにairlineのthemesも入れておきます。
Jetpack 'ryanoasis/vim-devicons'をinit.vimに追加して:JetpackSyncする。
公式のスクリーンショットでいい感じのを探す。

init.vimに下記を記述しました。

let g:airline#extensions#tabline#enabled = 1
let g:airline#extensions#tabline#buffer_idx_mode = 1
let g:airline_theme = 'minimalist'

(起動が0.5秒ぐらい遅くなった気がする)

nvim(テーマ)

Neovim用のcolorschemeの1つ。
github.com

colorschemeの決め手は猫のアイコン。

Jetpack 'vim-airline/vim-airline'をinit.vimに追加して:JetpackSyncする。
再起動すればcolorschemeに4種類ぐらい追加されている。

init.vimに下記を記述しました。

colorscheme catppuccin-mocha

見た目

こんな感じになりました。
大きな変化はないけどファイラーとか便利になった。


まとめ

自分はバニラのvimに慣れてしまっているのでNeovimの恩恵はあまり受けれていませんが, プラグインマシマシな人はvimよりNeovimの方が良いかも...と感じます。

プラグインを利用する場合はNeovim,それ以外はvimって感じで使い分けていきたいと思います。
(これからはNeovimにするつもり...)

あと,個人的にはWindowsの標準機能でインストール出来る点がめっっちゃ好きです。

gVimを触ってみた

ちは

gVimを触った時の備忘録です。


やりたいこと

なるべくCUIvimに見た目を近づけたい。

参考

Vim入門書です。


gvimとは

GUIvimです。

vimなのにマウスでポチポチ操作することも出来るので, 初めてvimを使ってみたい人にもおススメです。

オリジナル
www.vim.org

環境

ここでは日本人用に最適化されたgvimを使用します。

KaoriYaさんでコンパイル済みバイナリが配布されています。
www.kaoriya.net

  • Windows11
  • gVim: vim82-kaoriya-win64-8.2.1287-20200724

最終形態

この記事の設定を適用するとこうなります。

設定前

設定後


設定項目

雑にメモ

utf-8

switches/catalog のフォルダにある utf-8.vim を switches/enabled にコピーすると utf-8 に対応するらしいです。

scriptencoding utf-8 を .gvimrc に記述してもいけそう(?)

フォント変更

gvimの標準フォントはMS_GothicですがConsolasに変更します。

:set guifont=Consolas:h12
:set guifontwide=Consolas:h12

変更してから :h tutor でフォントを確認すると表示が崩れていました。

全角文字だけMS_Gothicにしたらいい感じになりました。

:set guifontwide=MS_Gothic:h12

renderoptions

文字の描画方法についてです。

renderoptions の設定のしかた によるとガンマレベルやアンチエイリアスの設定などが出来るようです。

自分はこだわりがないのでrenmodeだけおススメ設定にしました。
renmodeは[1,6]の範囲で設定でき,1から順にフォントが太くなります。

:set rop=type:directx,renmode:5

ダイグラフ(digraph)

.vimrcを.gvimrcに移植していた時にダイグラフ関係のエラーが出ました。

E474: 無効な引数です: listchars=eol:↓,tab:»-,trail:_

digraph - Vim日本語ドキュメント によるとマルチバイト文字の「↓」が問題ぽいです。

bashなどが使える環境なら
setenv LC_CTYPE en_US.utf8
を.bashrcなどに記述することで黙らせる事が出来ますが, gvimではこの技は使えなさそう(?)

全角記号が入っているのも気持ち悪いので, おとなしく標準ダイグラフの「¿」に変更しました。
set listchars=eol:¿,tab:»-,trail:_

encoding関係を頑張ればマルチバイトもいけるはず...

(補足)
:source .gvimrc で再読み込みすると上記エラーが出ますが, 再読み込みしなければエラー無く普通に「↓」が表示されました。
よくわからない。

ツールバーを非表示

guioptions を設定すれば色々非表示に出来そうです。

メニューバー
:set guioptions-=m

ツールバー
:set guioptions-=T

スクロールバー
:set guioptions-=r
:set guioptions-=R
:set guioptions-=l
:set guioptions-=L
:set guioptions-=b

まとめて設定できる方法はわかりませんでした。
:set guioptions-=mTrRlLb ではダメっぽい(?)

起動時のウィンドウサイズ

標準だと正方形で使いにくかったので修正しました。

:set columns=125
:set lines=25


.gvimrc

.vimrcにif has('gui_running')を定義して,そこに記述しておくのが良さそうですが,GUI関係の設定は.gvimrcでしか読まれないっぽいです。

.vimrcを.gvimrcとしてシンボリックリンク張れば読めるので, 新しく.gvimrcを作成するよりかは楽だと思う。
まあ,Windowsだとシンボリックリンク張るよりはコピペすることになると思うけど。

基本的には上記の設定を.vimrcに記述しただけ。

雑にgvim用のcolorschemeとlistcharsを再定義しているのは内緒。

あと,ファイルの意味を考えるとGUIに関する設定は.gvimrc, vimに関する設定は.vimrcに記述して住み分けするのが良さそう。


まとめ

GUIの利点を殺しているのでCUIで良い気もする。

gvimはインストール不要なのでvimがインストールされていない環境では役に立つかも。
(そんな環境でフリーソフトが利用できるのかは別の話)


C++にluaを組み込んでみた

こんちには

C++luaを連携させた時の備忘録です。

この記事は2022年12月時点の内容なので,未来から来た人は少し事情が違うかも。
Luaのバージョンは5.4.2です。

個人的な見解が入っているので参考程度に見てください。


連携すると何が出来るの?

C++からLuaを呼び出す/呼び出せることでプログラムデータのように扱えるようになります。

よくゲーム開発で利用されているのでゲームを例として

場合を考えます。

なんとなく想像がつくと思いますが,ぐねぐね動く・追尾するなどの行動を30個ぐらいプログラムしておき,それを組み合わせて実質行動が異なる100体の敵を作成するのが主流です(意見はあると思いますが…)

このとき行動パターンの「プログラム」はプログラマが作成し,その行動パターンの「データ」を組み合わせて敵AIをプランナが作成するようにすると,プログラマは制御処理に集中でき,プランナはゲームの面白さに集中できるようになるのでお互いの得意な領域で力が発揮できるようになります(よね)

要は「同じ処理を利用して,異なるプログラムを大量生産したい」ときにLuaなどのスクリプト言語が(コンパイルなどの面から見ても)便利です。
このような性質から比較的規模が大きく,大量生産が必要な環境などで力を発揮しやすいのかなと個人的には思っています。

また,スクリプト言語Luaである必要はありませんが,歴史的な背景からC++から呼び出されるのはLuaが主流です。
気になった方はLuaWikipediaを読んでみてください。
ja.wikipedia.org


参考

サイト

APIの利用方法を参考にさせていただきました。
inzkyk.xyz

書籍

少し古いですが考え方は参考になると思います。


読みやすかったです(お値段的にも)

関連

よかったらこっちも見てね。
lambda00.hatenablog.com


開発環境

セットアップ

Windows + Visual Studio の条件下での初期設定についてです。
LuaはバージョンによってAPIの引数が多々異なるので注意してください。

1. Luaのダウンロード

公式サイトからソースとバイナリのどちらかを落とします。
www.lua.org

Linux向けにはMakefileが提供されていますが, Windowsはmakeするのが面倒なのでバイナリで落とした方が楽だと思います。

download -> binaries -> History から 「Lua 5.4.2 - Release 1」 を選択してsourceforgeへ飛びます。
Windows Libraries -> Static の順に辿り 「lua-5.4.2_Win64_vc16_lib.zip」をダウンロード。

ダウンロードしたzipを適当な場所に配置します。

2. Visual Studioのプロジェクト設定

適当にプロジェクトを作成し,ソースコードを追加しておきます。
ここでは「空のプロジェクト」に「main.cpp」を追加したことにします。
また,今回は64ビット版を使用するのでx86 から x64 にビルド設定を変更しておきます。

プロジェクト -> プロパティ を選択してプロパティ設定画面を開きます。

  1. C++17 設定
    構成プロパティ -> 全般 から C++言語標準を C++17 に設定。
    C++17に設定しましたが,別に設定しなくても大丈夫です。

  2. luaインクルードファイル 設定
    構成プロパティ -> C/C++ から 追加のインクルードディレクトリ に解凍したluaのincludeを指定します。
    Cドライブ直下なら「C:\lua-5.4.2_Win64_vc16_lib\include\」となります。

  3. リンカ 設定
    構成プロパティ -> リンカー -> 入力 から 追加の依存ファイル に解凍したluaのライブラリを指定します。
    Cドライブ直下なら「C:\lua-5.4.2_Win64_vc16_lib\lua54.lib」となります。

3. ビルド

ここまでで #include <lua.hpp> がコンパイル出来ることを確認しておくと良いです。
こける場合はプロパティの構成やプラットフォームが適切か確認してみてください。

Debug/Release の x64 で実行してみよう。

#include <lua.hpp>

int main()
{
    lua_State* pL = luaL_newstate();

    lua_close(pL);
    return 0;
}

C++Lua の連携

個人的によく使うだろうと思われる部分のみ記述しました。
他にも色々あるのでよかったら探してみてね。

また,ソースコードが冗長になりますが,途中から見てもわかるようにプログラムは差分ではなく,全文載せるようにしています。
APIの詳細についてはリファレンスを見てね。

1. Luaファイル読み込み

C++からLuaファイルを読み込みます。
Visual Studioからデバッガにアタッチして実行する場合はソリューションファイルの場所に配置するのが良いと思います。

#include <lua.hpp>
#include <iostream>

int main()
{
    lua_State* pL = luaL_newstate();

    if (luaL_dofile(pL, "./sample.lua") != LUA_OK)
    {
        // エラーコードを吐き出して終了
        std::cerr << lua_tostring(pL, lua_gettop(pL)) << std::endl;
        lua_close(pL);
        return 1;
    }
    lua_close(pL);
    return 0;
}
test1 = 100;
test2 = 200;
test3 = test1 + test2;
test4 = "AIUEO";
test5 = test 1 + test 2

luaL_dofile(pL, "./sample.lua")luaファイルを読み込んでいますが,luaファイルに構文エラーなどがあるとエラーコードをスタックトップに配置してくれます。
C++自体は終了しないのでエラーハンドリングは忘れずに。

出力結果

./sample.lua:5: unexpected symbol near '1'

2. C++からLuaグローバル変数を呼び出す

C++からLuaグローバル変数を呼び出します(luaファイルは上と同じ)
Luaはスタックマシンなので,pushする順番に注意。

#include <lua.hpp>
#include <iostream>

int main()
{
    lua_State* pL = luaL_newstate();

    if (luaL_dofile(pL, "./sample.lua") != LUA_OK)
    {
        // エラーコードを吐き出して終了
        std::cerr << lua_tostring(pL, lua_gettop(pL)) << std::endl;
        lua_close(pL);
        return 1;
    }

    std::cout << "Sample: variable" << std::endl;
    {
        lua_getglobal(pL, "test1");
        lua_getglobal(pL, "test3"); // test3だよ
        lua_getglobal(pL, "test2"); // test2だよ
        lua_getglobal(pL, "test4");

        const int num = lua_gettop(pL);
        for (int i = 1; i <= num; ++i)
        {
            const int type = lua_type(pL, i);
            std::cout << "type=" << type << ", " << "name=" << lua_typename(pL, type) << ", " << "val=" << lua_tonumber(pL, i) << ", " << "str=" << lua_tostring(pL, i) << std::endl;
        }
        lua_pop(pL, num);
    }

    lua_close(pL);
    return 0;
}
test1 = 100;
test2 = 200;
test3 = test1 + test2;
test4 = "AIUEO";

変数の型に応じて呼び出すluaAPIが変わる点に注意。
あと,使い終わったらlua_popでスタックをお掃除しようね。
出力結果

Sample: variable
type=3, name=number, val=100, str=100
type=3, name=number, val=300, str=300
type=3, name=number, val=200, str=200
type=4, name=string, val=0, str=AIUEO

3. C++からLuaの関数を呼び出す

Luaの特徴として複数の戻り値を返せます。
いずれにせよ操作はスタックなのでそんなに難しくないね。

関数はグローバルな点に注意。

#include <lua.hpp>
#include <iostream>

int main()
{
    lua_State* pL = luaL_newstate();

    if (luaL_dofile(pL, "./sample.lua") != LUA_OK)
    {
        // エラーコードを吐き出して終了
        std::cerr << lua_tostring(pL, lua_gettop(pL)) << std::endl;
        lua_close(pL);
        return 1;
    }

    std::cout << "Sample: function C++ call Lua" << std::endl;
    {
        // 0: フィボナッチ数列を計算させる
        const int nargs0 = 1;    // 引数の数
        const int nresults0 = 1; // 戻り値の数
        const int msgh0 = 0;     // メッセージハンドラ
        lua_getglobal(pL, "fibonacci");
        lua_pushnumber(pL, 10);
        if (lua_pcall(pL, nargs0, nresults0, msgh0) != LUA_OK)
        {
            std::cerr << lua_tostring(pL, lua_gettop(pL)) << std::endl;
            lua_close(pL);
            return 1;
        }
        std::cout << "\t" << "fibonacci=" << lua_tointeger(pL, 1) << std::endl;

        const int num1 = lua_gettop(pL);
        lua_pop(pL, num1);

        // 1: 複数の値を処理させる
        const int nargs1 = 2;    // 引数の数
        const int nresults1 = 3; // 戻り値の数
        const int msgh1 = 0;     // メッセージハンドラ
        lua_getglobal(pL, "multi");
        lua_pushnumber(pL, 3);
        lua_pushnumber(pL, 7);      
        if (lua_pcall(pL, nargs1, nresults1, msgh1) != LUA_OK)
        {
            std::cerr << lua_tostring(pL, lua_gettop(pL)) << std::endl;
            lua_close(pL);
            return 1;
        }
        std::cout << "\t" << "return=" << lua_tointeger(pL, 1) << ", " << lua_tointeger(pL, 2) << ", " << lua_tostring(pL, 3) << std::endl;

        const int num2 = lua_gettop(pL);
        lua_pop(pL, num2);
    }

    lua_close(pL);
    return 0;
function fibonacci(val)
    if val <= 0 then
        return 0;
    elseif val == 1 then
        return 1;
    end
    return fibonacci(val-1) + fibonacci(val-2)
end

function multi(val1, val2)
    return val1 + val2, val1 * val2, "aiueo"
end

出力結果

Sample: function C++ call Lua
        fibonacci=55
        return=10, 21, aiueo

4. LuaからC++の関数を呼び出す

おそらく一番よく使う用途だと思う。
基本的にはC++側からLuaで使用したい関数のコールバックを登録するだけ。

#include <lua.hpp>
#include <iostream>

int UltimateFunction(lua_State* pL)
{
    // Luaからの引数を取得する
    const lua_Number ret = lua_tonumber(pL, 1);
    lua_pop(pL, lua_gettop(pL));

    // Luaに渡す戻り値を詰める
    const int val = static_cast<int>(ret) * 2;
    lua_pushnumber(pL, val);

    const int returnLuaNum = 1; // Luaに渡す戻り値の数
    return returnLuaNum;
}

int main()
{
    lua_State* pL = luaL_newstate();

    if (luaL_dofile(pL, "./sample.lua") != LUA_OK)
    {
        // エラーコードを吐き出して終了
        std::cerr << lua_tostring(pL, lua_gettop(pL)) << std::endl;
        lua_close(pL);
        return 1;
    }

    std::cout << "Sample: function Lua call C++" << std::endl;
    {
        // コールバック登録
        const char luaFuncName[] = "Ultimate"; // Lua側で呼び出したい関数名
        lua_register(pL, luaFuncName, &UltimateFunction);

        const int nargs = 0;    // 引数の数
        const int nresults = 1; // 戻り値の数
        const int msgh = 0;     // メッセージハンドラ
        lua_getglobal(pL, "master");
        if (lua_pcall(pL, nargs, nresults, msgh) != LUA_OK)
        {
            std::cerr << lua_tostring(pL, lua_gettop(pL)) << std::endl;
            lua_close(pL);
            return 1;
        }
        std::cout << "\t" << "master=" << lua_tointeger(pL, 1) << std::endl;
    }

    lua_close(pL);
    return 0;
}
function master()
    return Ultimate(1567)
end

C++ から Lua を呼び出し,Lua から C++を呼び出しているけど,そんなにややこしくないはず。
出力結果

Sample: function Lua call C++
        master=3134

5. コルーチンの利用

Luaの特徴であるコルーチンをC++から利用してみます。
他の言語と同じくyieldとresumeが鍵です。

#include <lua.hpp>
#include <iostream>

int main()
{
    lua_State* pL = luaL_newstate();

    if (luaL_dofile(pL, "./sample.lua") != LUA_OK)
    {
        // エラーコードを吐き出して終了
        std::cerr << lua_tostring(pL, lua_gettop(pL)) << std::endl;
        lua_close(pL);
        return 1;
    }

    std::cout << "Sample: Coroutine" << std::endl;
    {
        luaL_openlibs(pL);

        lua_State* pCoroutine = lua_newthread(pL);

        lua_getglobal(pCoroutine, "co_routine");

        lua_State* from = nullptr; //再開させるコルーチン
        const int nargs = 0; // Luaに渡す引数の数
        int nret = 0;        // Luaからの戻り値の数
        int cnt = 0;         // ループカウンタ
        while (true)
        {
            const int ret = lua_resume(pCoroutine, from, nargs, &nret); // コルーチン開始/再開
            if (ret == LUA_OK) { break; } // コルーチン終了

            // エラーハンドリング
            // - LUA_OK or LUA_YIELDでない時点でおかしい
            if (ret != LUA_YIELD)
            {
                std::cerr << lua_tostring(pCoroutine, lua_gettop(pCoroutine)) << std::endl;
                lua_close(pL);
                return 1;
            }

            // コルーチン処理
            // - retに戻り値の数が入っている
            std::cout << "\t" << "cnt=" << cnt++ << ", " << "ret=" << nret;
            for (int i = 0; i < nret; ++i)
            {
                std::cout << ", " << lua_tostring(pCoroutine, i + 1);
            }
            std::cout << std::endl;
        }

        const int num = lua_gettop(pL);
        lua_pop(pL, num);
    }

    lua_close(pL);
    return 0;
}
function co_routine()
    coroutine.yield("Blue", "RED");
    coroutine.yield("Eyes", "EYES", 1);
    coroutine.yield("White", "BLACK", 1, 2);
    coroutine.yield("Dragon", "DRAGON", 1, 2, 3);
end

lua_resume()で再開させない限りLuaのスレッドは停止していますね。
これはいろいろと便利。
出力結果

Sample: Coroutine
        cnt=0, ret=2, Blue, RED
        cnt=1, ret=3, Eyes, EYES, 1
        cnt=2, ret=4, White, BLACK, 1, 2
        cnt=3, ret=5, Dragon, DRAGON, 1, 2, 3

感想

ざっくりと個人用にC++Luaのよく利用する連携をまとめてみました。

C++Luaの連携は良い点もありますが,Lua仮想マシン(LuaVM)上で動作する都合上,解析されやすい点がデメリットに挙げられますね。
luaをバイナリ化する方法もありますが,C++で作成されたバイナリよりは読みやすいので解析防止に有効とは言えませんがしないよりは良いかもしれません...
また,速度面もかなり違うので重い処理はC++で行うのが良いと思われます。

C++だけでプログラムを作成するのと,C++Luaを組み合わせてプログラムを作成する方法にはそれぞれ利点があるので使い分けていきたいと思いました。

おわり

C++: 標準出力に色をつけてみた

こんちは

C++で標準出力(std::cout)に色を付けた時の備忘録です。


何が出来るの?

標準出力で色付き画像の表示ができるようになります。

つまり

  • OpenGLDirectXなどの代表的なグラフィックライブラリは不要
  • 標準ライブラリのみで描画が可能
  • わりと簡単

なので,CUI環境でも画像処理や3DCGが捗るよ!!!

あと,簡単に画像を確認したいときに単色よりは役立つよね。


環境

2つの環境で開発し,実行結果を確認しました。

OS

言語

開発環境1

開発環境2

  • WSL2 Arch Linux
  • g++ 11.1.0

参考文献

こちらの記事を参考にさせて頂きました。

en.wikipedia.org
www-he.scphys.kyoto-u.ac.jp
nodachisoft.com
daeudaeu.com


原理

察しが付くと思いますが「エスケープシーケンス」を利用しただけです。

例えば [0, 255] のRGB色の文字を表示する場合のエスケープシーケンスは

\x1b[38;2;<R>;<G>;<B>m

です。

雑な解説

「\x1b」は16進数のASCIIコードでESC(エスケープ)を意味します。
その次の「[38」は文字のカラーコード指定,「2;」が[0,255]のRGB指定です。
その後に「R;G;B」で[0,255]を指定し,「m」でエスケープシーケンスを締めます。

つまり

std::cout << "\x1b[38;2;10;20;30m" << "A I U E O" << std::endl;

は「A I U E O」をR=10, G=20, B=30の色の文字で表示しろという命令となります。

文字ではなくて背景色を変更したい場合は「48」を指定します。
表示行の色が変化するため改行前に「\x1b[m」で元に戻すことをおススメします。

std::cout << "\x1b[48;2;10;20;30m" << "A I U E O" << "\x1b[m" << std::endl;

画面クリアの実装はカーソル移動のエスケープシーケンスを使用します。

\x1b[K      // カーソル位置から右側を消去 
\x1b[<num>A // カーソルを<num>行移動  

表示した分だけ上って削除を繰り返せば良いですね。
雑に実装するとこんな感じ。

for (uint32_t i = 0u; i < height; ++i)
{
  for (uint32_t j = 0u; j < width; ++j)
  {
    std::cout << "\x1b[1A" << "\x1b[K";
  }
}

実装

エスケープシーケンスの使い方さえわかれば実装は簡単ですね。
C++で実装しましたが,CでもPythonでも何でも大丈夫なはずです。

自分は配列にRGB値を押し込んで表示するようにしました。
最も簡単な方法だと思います。

const uint32_t width = 200u;
const uint32_t height = 100u;
uint32_t* pImage = new uint32_t[width * height];

// x, yが座標,valに画素値が入っているとする
// uint32_t val = (b << 16) | (g << 8) | (r << 0);
pImage[y * width + x] = val;

何となく表示速度が問題になりそうな気がしたので, 一度文字列として表示する情報を構築してから最後にstd::cout に流し込むようにしました。

フラッシュの速度などは計測していませんが,逐次フラッシュをかけるよりは速いと思っています。

標準出力に結果を表示する際は更新速度が速すぎると表示がおかしくなるので, 表示完了後から何秒か待ったほうが良いです。
ダブルバッファに対応したり,描画と処理のスレッドを分けたりするといい感じになると思いました。

自分の実装はこちら。
github.com

Windowsの人向け

Visual StudioC++コンパイラを利用している時に, エスケープシーケンスが上手く処理されない場合がありました。

そんな時はWindows.hの「ENABLE_VIRTUAL_TERMINAL_PROCESSING」のフラグを立てましょう。

HANDLE stdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD mode = 0;
GetConsoleMode(stdHandle, &mode);
SetConsoleMode(stdHandle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);

結果

PGMやPPM形式が扱いやすかったので,何年か前に1週間レイトレで作成した画像を表示してみました。
(実行するときはコンソールをめっちゃ縮小してウィンドウ最大化するのをおススメ)

実行するとわかりますが,カーソル移動のエスケープシーケンスは表示行と論理行を区別します。
よく考えると当たり前ですが,表示が上手くクリアされずに5分ぐらい悩みました。

あと,環境によっては上手くエスケープシーケンスが処理されないようです。
MSYS2のg++でコンパイルして実行したときは処理されていませんでした。
おま環な気がする(?)

200×100: 文字色(\x1b[38)

200×100: 背景色(\x1b[48)

800×400: 文字色(\x1b[38)

800×400: 背景色(\x1b[48)

1週間レイトレ(Ray Tracing in One Weekend)はこちら。
raytracing.github.io

1週間レイトレに取り組んでいる時は,標準出力をリダイレクトして作成したPPM形式のファイルをinkscapeで表示していましたが, エスケープシーケンスを利用すれば別アプリを立ち上げなくて済みますね。

カーソル移動などを工夫すればリアルタイムに更新した結果を表示出来るので楽しいかも。
(某ベンチマークソフト的な描画もできるかもね)


感想

最終的にエスケープシーケンスを利用してコンソール上に色つきの画像を表示してみました。

色分解はあまり高くなく,表示速度や疑似輪郭の問題はありますが, CUI環境でもCGで遊べる環境が作れることが分かったので良かったです。

標準機能のみで遊べるので, CUI用の自作レンダラとか作れると激アツかもね。

おわり

Cコンパイラ自作に入門してみた

ちは

Cコンパイラの自作に入門してみた感想です。


参考文献

こちらのサイトを参考に実装を進めました。

www.sigbus.info

ステップ12以降は執筆途中(221207)とのことですが, ここまでの知識と提供されているソースコードを確認しながら進めれば問題なくセルフホストのCコンパイラを作成できると思います。

参考ソースコードはこちら
github.com


なぜ作ろうと思ったのか

プログラマコンパイラを作るのは当然のこと・・・

アセンブラの知識があまりにも未熟で自作OSの入門書が読めなかったことが始まりで, 楽しく手を動かしてアセンブラに触れられるものはないかなぁと探しているときに上記のコンパイラ自作のサイトに出会いました。

表題の「低レイヤを知りたい人のためのCコンパイラ作成入門」からわかる通り, x86-64命令セットを使用しながらセルフホストのCコンパイラを作成するまでの過程が解説されていて, 自分の「手を動かしてアセンブラに触れる」という目的と合っていたので取り組んでみた感じです。

ちなみに自分のレベルは

  • 学生の時に言語処理系の講義を取ってた
  • 電卓に変数を導入した超簡単な自作言語のインタプリタ作成

です。

きっかけとなった自作OS本はこちら


環境

WSL2でも良かったのですが,何となく仮想マシンUbuntuを立てて行いました。


成果

とりあえず1週間以内でセルフホスト達成を目標に取り組みましたが, 自分の能力では「あらかじめ用意されたC言語の引数なし関数を自作コンパイラの出力とgccでリンクして動作させることが出来る」までしか実装できませんでした。
(参考文献の「ステップ14: 関数の呼び出しに対応する」の途中までです)

こんな感じの構文を読むことが出来ます。

関数は事前に戻り値・引数なしの関数をC言語で作成して,そのオブジェクトファイルを出力したアセンブラとリンクしないと動作しません。

要は電卓に変数・if-else・while・スコープの概念を取り組んだだけ。
(関数電卓にこの機能があれば便利そうだと思った(小並感))

もっと時間をかけてソースコードを参考にしながら取り組んでみたかったのですが, 自作OS入門書を読むという目的から外れてしまいそうだったのでとりあえず1週間の結果で終了。

テストはこんな感じになりました。

ソースコードはこちら(コミットやブランチはテキトウ)
github.com

いっちょ前にfree()とかを使って個性を出していますが,本文を読むとわかる通り使わない方が簡単です。。。
最終的にメモリリークや冗長な処理の最適化などは面倒になって投げてます。。。

まあ,完成しない神コードより完成したクソコードの方が正義だしね(?)


反省

初めに全て読み終えてからプログラムを書き始めた方が良かったなと思いました。

冗長な部分を書き直したり,メモリ解放のコードを書いたりしましたが, 本文中に冗長な理由やメモリ解放を敢えて行っていない理由が書かれています。

取り組む前によく確認する癖は重要だと感じました。


感想

セルフホストは達成できませんでしたが,コンパイラ自作は「コンパイラというモンスター」を自作する育成ゲームをしているようでめっっちゃ面白かったのです。

特に出力されたアセンブラをテストするスクリプトを書き,そのテストが試行錯誤の果てに通った時は化け物を錬成した気分になりました(?)

次は限界まで時間をかけてセルフホストのコンパイラを育成してみたいと思います!

おわり

「基礎からのMySQL」を読んだ

こんにちは


基礎からのMySQLを読んだ備忘録です。


書籍

図書館で借りました。

対象

・何も知らない状態でSQLを学びたい人
SQLが何なのかよくわかっていない人

業務でSQLをちょいちょい見ることがあるので勉強のために読み始めました。

SQLの知識は「データベース関係のやつだな」程度で全く知りませんでしたが, 本書を読んでSQLの概要を知ることは出来たと思います。

SQLの知識がほとんどないので正当な評価が出来ているとは思えませんが, 本書はSQLを全く知らない人向けの内容で, MySQLの詳細やSQLの深い知識を求めている人向けでは無い気がします。

ただ,SQL入門として初めに読む1冊としては 挫折せずに最後まで読み進められると思うので結構良いのかなと感じました。

本書は簡単なWEBサイトの構築まで行います。


メモ

雑に

・クエリ
 データベースに処理する内容を指定するコマンド
 -> 問い合わせ

・Structured Query Language (SQL)
 クエリを記述するプログラミング言語
 -> IBMが始祖

MAMP
 Macintosh, Apache, MySQL, PHP
 -> XAMPP, LAMP

localhost
 ローカル・ループバック・アドレス

・cmd中にF7
 入力履歴を表示
 -> MySQL関係ないけど,知らなかったのでメモ

・コピー

CREATE TABLE テーブル名 SELECT * FROM 元となるテーブル名;  
CREATE TABLE テーブル名 LIKE 元となるテーブル名;  
INSERT INTO テーブル名 SELECT * FROM 元となるテーブル名;

・条件

SELECT カラム名
  CASE
    WHEN 条件1 THEN 表示する値
    WHEN 条件2 THEN 表示する値
  END AS エイリアス
FROM テーブル名

・並び替え
SELECT * FROM テーブル名 ORDER BY カラム名 DESC LIMIT 数値;
 OFFSETで表示開始レコードのシフト数を指定可能。
SELECT * FROM テーブル名 ORDER BY カラム名 DESC LIMIT 数値 OFFSET 数値;

・HAVINGとWHERE
 HAVING: グループ化した値
 WHERE: グループ化する前の値

・UNION

SELECT カラム名1 FROM テーブル名1 UNION SELECT カラム名2 FROM テーブル名2;

ALL: 重複を許容

・内部結合
一致するレコードを取り出すような結合

SELECT カラム名 FROM テーブル1 JOIN 結合するテーブル2 ON テーブル1カラム=テーブル2カラム;

・外部結合
一致かどうか関係なく,一方のテーブルのレコードを全て取り出す
-> 左外部結合と右外部結合があり,混同はあまり良くない。

・サブクエリ
クエリ実行によって得られたデータにクエリを発行する。

SELECT * FROM テーブル名 WHERE カラム名 IN (1段階目の処理結果);

・ビュー
テーブルのように見えるけど実体がないデータの集合情報
-> 仮想的なテーブルのイメージかな?

CREATE VIEW ビュー名 AS SELECT カラム名 FROM テーブル名 WHERE 条件;

・ストアドプロシージャ
複数のSQL文を1つにまとめたもの
未完成状態で実行されるのを防ぐためにデリミタを定義する。

delimiter //
CREATE PROCEDURE ストアドプロシージャ名()
BEGIN
  SELECT * FROM テーブル名;
  SELECT * FROM テーブル名;
  SQL文が続く…
END
//

CALL ストアドプロシージャ名;

・トリガ
テーブルに処理が走ると,それをトリガとしてコマンド実行する仕組み

・ストレージエンジン
データベースを操作するエンジン的なものなのかね?

SHOW CREATE TABLE テーブル名;

トランザクション
コミットとロールバックと一緒によく出てくるやつ。


感想

SQLについて全く知らない状態で読み始めましたが, SQLが何者で何に利用されているかなどの基礎知識は身につけられたので良かったです。

特にWEBサイトを構築する際に気を付ける項目(SQLインジェクションなど)についても解説されており, 今まで触れてこなかったWEB系の技術についても簡単に学べたので勉強になりました。

いつか自分のサイトを作ってみたい。

「Winnyの技術」を読んだ

こんにちは

Winnyの技術を読んだ感想,レビュー,備忘録的なのです。


書籍

達人出版会で購入しましたが,PDFで読めるのがいいですね。
tatsu-zine.com

Amazonは実本しかないようで,プレミア価格っぽいです。

想定読者

  • P2Pの技術に興味があるけど知識がない人
  • ネットワーク技術を学習する取っ掛かりがほしい人
  • Winnyの技術に興味がある人

内容はWinnyに使用されている技術の解説ですが,P2P型のシステムなのでP2Pの解説が大部分を占めています。
P2Pの入門書としては読みやすく,技術解説も詳細に記述されているので,NFTなどでP2Pに興味を持った人が初めに読む1冊としても良いと思います。
ただし,執筆日が2005年なため情報が古い場合がある点には注意。

図を多用して解説されているので読みやすいネットワークアプリ開発の入門書を探している人にもおススメですが,既に何冊か大学講義で使用されているレベルのネットワーク関係の本を読んだことがある人は既知の内容かもしれません。
ただ,Winny開発者でもある著者の思想やP2P型のファイル共有ソフトへの考え・歴史,Winnyを運用して得られた知見なども記述されているので,その点では面白く読めると思います。

(関係ないけど)本文中のいくつかの単語は3音ルール(JIS Z 8301)で書かれており,個人的に読みやすかったです。
サーバーとサーバだったら後者が好き。


メモ

ウルトラ雑

1章 P2Pの基礎知識

・Peer to Peer(P2P)
 主従関係のないシステムモデル
 -> P2Pソフトの中で(当時)最も普及したものがファイル共有ソフト
 サーバは存在せず,各ピア(ノード)が状況に応じて変化する。
 -> P2Pのピアはサーバント(サーバ兼クライアント)とも呼ぶらしい

・クライアント・サーバ方式
 サーバ: クライアントからの接続を受けてサービス提供
 クライアント: サーバに接続して一方的にサービスを利用
 -> FTP, TELNETなど (関連技術の説明に時代を感じますね)

P2P型システムの特徴
 1. システム拡大に強い
  C/S方式ではサーバへのリクエスト増加でネットワーク負荷が高まる
  -> P2P型は各ノードがサーバ機能を持つため負荷が集中することがない
 2. 障害に強い
  C/S方式ではサーバが停止するとシステム全体が利用不可
  -> P2P型はノードの一部が停止しても他ノードを利用すればよい
 3. データの一元管理・短時間同期は困難
  P2P型はデータがネットワークに分散している都合上,管理が困難
  -> C/S方式はサーバが管理できる
  P2P型は一度稼働するとシステム停止が実質的に不可能
  -> 全ノードを管理することが出来ないため

P2PとC/Sのどっちが良いか
 ただの手段に過ぎないので,利用用途に合わせて選ぼうね
 P2P: 利用者の資源を有効活用可能 (NFTとか見るとそうだよね)
 C/S: データの一元管理が可能

ファイル共有ソフトの世代
 第一と第二の違いは分かるが,第三は諸説ありそう
 1. 第一世代: Napster
  サーバの集中管理
 2. 第二世代: Gnutella
  サーバ不要のノード間やり取り
 3. 第三世代: Winny
  キャッシュ機構,匿名性を持つ

・レジューム機能
 ファイル転送失敗時に途中から再開する機能

・キャッシュ機構と匿名性
 多数のノードを経由すればキャッシュか発信元なのか区別できない
 -> キャッシュファイルの量が匿名性に関与

WinnyFreenet
 WinnyFreenetを参考にして誕生した
 -> 匿名性はFreenet
 -> 匿名と効率のトレードオフを取ったのがWinny

2章 Winny紹介

技術的な詳細はコピペになるので省略します。本書を見てね。

Winnyのコンセプト
 -> コンセプトから分かる通り,利用者のことも考えている
 -> 著者はインタプリタVM系の言語は好きではないっぽい(効率重視)
 1. 匿名性
 2. 共有効率
 3. Windowsネイティブ

Freenetの課題点
 ネットワーク規模に比例して検索依頼の効率が低下する
 -> 資源は有限なため,理論上は可能であっても効率がかなり悪い
 -> WinnyFreenetの「情報の間接的なやり取り」に注目した

・プロクシーサーバ(プロキシサーバ)
 FWでさえぎられているLAN内のノードと外部ノードとの通信に使用
 -> 外部ノードからはプロキシサーバが通信相手であり,LAN内の通信相手は不明
 -> 通信の仕組み上,IPアドレスは開示されるが,中継ノードを挟めば有耶無耶に…
 プロキシサーバは中継時にデータをキャッシュする
 -> 同データを要求された場合はキャッシュを利用

Winnyはプロキシサーバから着想を得た
 プロキシサーバはC/S方式を前提とし,P2Pに応用するには...
 -> プロキシサーバを内包したファイル共有ソフト

Winnyの開発目的は技術的な検証
 P2P型のファイル共有システムという性質上,使用してもらわないと評価出来ない
 -> ユーザ資源(環境)は様々で,先行事例もなかったから
 -> 検証が目的だったので,Winny1は正式版をリリースして開発を終了

・Winny2
 Winny1上に大規模BBSを実現することが目的
 -> 開発環境をVisual C++ から C++ Builderへ(GUI関連が目的)
 -> あくまで分散BBSの開発が目的

3章 Winnyの仕組み

Winny特有の仕組みについての技術解説。
かなり踏み込んでいるので詳細は云々。

Winnyネットワーク
 インターネット状にWinnyノードが相互に構築したネットワーク
 -> ノード参加・離脱が頻繁な動的ネットワーク
 -> ネットワーク全体の機能を保つ必要がある
 -> 上流・下流の概念があるので転送ノードを限定できる

Winnyノード
 FTTH, ADSL, ISDNなどの順に重みづけ

・上流
 処理能力の高いノード
 -> キーとキャッシュファイルを多数集め,効率を高める
 -> 木構造の根
 -> 構造的にキーを多数保持するので検索がヒットしやすい

下流
 処理能力の低いノード
 -> 木構造の葉
 -> キャッシュ済みか検索結果を受信したキーが多数
 -> 上流に向かって検索クエリを飛ばす

クラスタリング
 集めているファイルの傾向が似たユーザ同士をまとめて効率を高める
 -> 自動ダウンロード機能に利用するキーワードでクラスタリング
 -> 上流・下流クラスタリングの概念を取り入れたことがWinnyの売り
 -> クラスタが異なれば同一LAN内でもWinnyネットワークでは距離が遠い

・効率の良いネットワーク形態
 ノード間のホップ数を短くする
 -> 転送時間が短くなり,経由ノードへの負担が小さくなる
 -> 理論上はフルメッシュが最強だけど,ネットワーク規模的に不可能
 -> 1960年,ミルグラムの手紙やり取りの実験

4章 実装

Winnyの実装がガチガチに解説されている。
利用者を意識した設計についても言及されているので,P2Pに限らずユーザ目線のアプリ開発にも参考になるかも。

・バージョン
 新バージョンのノードを見つけると警告メッセージを表示
 -> プロトコルの互換性によって通信できなくなるのを防ぐため
 -> 利用者に寄り添っていたことが伺えるね。

クラスタリングの評価
 「好みが近い」という主観的な情報から相関性をどう求めるか
 -> 3つのキーワードを設定し,近似度から論理距離を求める
 -> 一致文字数の合計で相関度を評価する都合上,短いキーワードが有利
 -> 仕組みを理解してなくても使用でき,理解していれば自由に渡り歩ける
 この辺は考察しがいのある面白い箇所だと思う。
 -> キーワードを意図的に長くし,ノード間の相関係数を高めるなど

・ハッシュはMD5
 脆弱性の観点からSHA-1に置き換えることを検討していた
 -> MD5SHA-1ってとこに時代を感じる

・ノードの処理能力を加味したルール
 当時はADSLが主流だが,光回線より効率が悪い
 -> ADSLを制限すればダウンロード総量を減らせるが,Winnyネットとしては遅い
 -> 上流と下流の性能を考慮したチューニングが必要

・自前タスク管理
 OSのマルチスレッド機能に頼ると面倒なバグに直面しそうだがら自前したらしい
 -> デッドロックデバッグなどがやっかいだと思っていた
 -> これまでの経験則なんだろうね
 -> ノンプリエンプティブマルチタスク

5章 P2Pソフトの開発手法

Winnyの運用を通して得られた知見について。
運営型のプロジェクトと見ると技術者は参考になる部分が多いと思う。

P2Pアプリケーションのテスト
 数台のPCだけでは限界がある。
 -> 大多数のネットワークアプリに当てはまるはず
 -> シミュレーションするのが良いが,最終的にはテスターを募る
 -> 問題点の解消はテスター離れを防ぐためにも迅速に!

・アプリケーション開発
 プログラムは気軽に作り変えるのはほぼ不可
 -> 互換性,ユーザ離れ
 開発が終了しない
 -> 公開されない神アプリ現象って勝手に思っている
 注目を集めると攻撃される
 -> 攻撃に対するテストもやろうね
 -> 偽りのバージョン警告を出されたりしたらしい

・シミュレーション
 最終的には動作しないとわからないけど...
 -> やってみないと分からない箇所もなんらかの検証は必要
 Winnyの開発前に取り組んだのはシミュレータ開発
 -> シミュレーション先行は基本だよね
 βテストで分かることも多い
 -> ユーザがポートを開いていない場合の対処など
 -> 基本的にユーザの振る舞いを予測することは困難

Winnyネットワークの状況把握は不可能か
 モニター機構を備えれば可能だが,P2Pシステムと相性が悪い
 -> 特定ノードへの接続がバレるとDDoSされるなど

・匿名性と転送効率はトレードオフの関係
 適切な中継発生率を求めて,効率を高める

Winnyと暗号技術
 暗号化は共通鍵,Winny2のBBS認証にはRSA,その他もろもろ(RC4とか)
 -> 執筆時点では通信内容がのぞき見されることはなかったっぽいね
 -> Winnyプロトコルをマネされたら面倒
 -> 基本的にはユーザ保存のものは公開データと考える
 基本的には暗号は破られるものと考える
 -> 破られても致命的な問題を起こさない設計
 IPアドレスなどの情報は暗号化してもすぐバレるけど,心理的な効果は期待できる
 -> クロールプログラム対策とか
 -> 平文よりは遥かにマシだしね
 パッカーを利用
 -> マルウェアとかで主流の手法だね

Winnyネットワーク/プログラムへの攻撃
 ・ゴミファイルのばら撒き
  -> クラスタリングの機能を逆手に取った攻撃
  -> ノード負荷の観点からもイヤッ(ちいかわ)
 ・ファイル捏造
 ・ウィルス問題
  -> Winnyが注目された最大要因だと思う問題
 ・プログラム改ざん
  -> ハッシュ値を確認して動作変更などの対策
  -> 他プログラムと同様,システム解析への時間稼ぎにすぎない

オープンソース
 フリーライドの増加などを考慮して行わなかった

6章 残された課題と可能性

将来の展望的なお話
論文みたいだね

・デジタル認証とアクセスコントロール
 特定ファイルの削除などは検討していたっぽい
 -> 情報流出やマルウェア騒動の対策としても必要だったと思う

ファイル共有ソフトの応用
 著作物の不正利用が問題になった
 -> 社会システムが技術の進歩に伴わなければならない事例
 -> インフラ利用も出来たのではないか

付録

キャッシュ構造やシミュレーション結果,Winnyプロトコル
結構面白い


感想

読みやすいネットワーク関係の本を探しているときに出会いました。

本書の大部分はWinnyの技術説明ですが,簡単なP2Pソフトを作成できる位の知識は身に付けられる内容だと思います。
また,内容に興味があったのでネットワーク技術の書籍としては個人的に読みやすく,飽きずに最後まで読み切れたので良かったです。

Winnyは社会に負の影響を与えた事は否定できませんが,開発に使用された技術や考え方は悪いものではないので, 頭ごなしに全てを否定するのではなく参考に出来る部分は参考にしていきたいですね。

ただ,Winnyは利用者に寄り添った実装を行っている部分が多々見受けられたので,開発者は利用者の使用用途をある程度は予測していたのではないかと感じました。
(まあ,この辺は終わった話なので深くは言及しませんが...)

P2P技術とソフトウェアの在り方について考えさせられる本でした。