OCamlのannotファイルの読み方

ocamlcやocamloptでnyan.mlを-annotオプションを付けてコンパイルするとnyan.annotファイルが生成される。 このファイルにはコンパイルするときに得られた識別子のスコープとか式の型とか関数呼び出しが末尾再帰かそうでないかとかの情報が含まれています。例えばテキストエディタが末尾再帰をハイライトする時にこのファイルを使えます。
annotファイルのフォーマットは中身を見れば大体分かるだろうけど、取り敢えず使いそうな事をメモしておきます。
この記事は雰囲気を感じ取って書いています。正式なフォーマットの解説とかあれば教えてください。

*はdon’t careだと思ってください。

annotファイルがどうとか以前に僕はOCaml自体についても初心者なので変なこと書いてるかもです。指摘していただけるとありがたいです。

全体

情報はそれぞれの式や識別子に対して

"ファイル名1" * * 開始位置 "ファイル名2" * * 終了位置
情報名1 (
  情報の詳細
)
情報名2 (
  情報の詳細
)
...

という形式で記述されています。
“ファイル名1"の"開始位置"から"ファイル名2"の"終了位置"までがその式や識別子の書かれている場所です。普通は"ファイル名1"と"ファイル名2"は同じになると思います。
情報名1や情報名2は型とか識別子とかそういうのです。 例えば

"main.ml" * * 8 "main.ml" * * 11
type(
  int -> int
)

って書いてあると先頭から8〜11の位置に書かれてあるものの型がintからintへの関数なんだなーって分かります。

識別子

識別子の宣言に対しては

ident(
  def 識別子 "ファイル名" * * 開始位置 "ファイル名" * * 終了位置
)

という情報がつきます。これは指している文字列は宣言された識別子であり、そのスコープは開始位置から終了位置であるということを示します。

また変数の使用については

ident(
  *_ref 名前 "ファイル名" * * 開始位置 "ファイル名" * * 終了位置
)

という情報がつきます。これは指している識別子が"名前"という名前の変数であり、その変数は開始位置から終了位置で宣言されていることを示します。
この"名前"はモジュール名込みの名前っぽいです。

関数呼び出し

関数呼び出しが末尾再帰であれば

call(
  tail
)

非末尾再帰であれば

call(
  stack
)

という情報がつきます。

WebAssembly手書き (wast) で簡単なSTGを作った

WebAssembly (wasm) のテキスト表現でwastというものがある。
僕の所属しているAmusementCreatorsの48時間ゲームジャムでこのwastをがりがり書いて簡単なSTGを作った。
デモはこちら https://akitsu-sanae.github.io/works/wast-game/index.html
ソースコードはここのgame.wastを見てください https://github.com/akitsu-sanae/wast-game

Web周りの知識に疎いので有名なブラウザで動かないようなコードを書いてたらごめんなさい。指摘してくれると嬉しいです。

wastを書いた感想は記述量の多い昔のC言語って感じだった。キーボードを打つのが疲れるけど思ってたよりマゾマゾしくない。

今回で得られた知見をまとめて一つの記事にする予定。WebAssembly手書きマン増えてくれ。

ゲームを作るにあたって参考になったものを上げておく

C/C++の可変長引数マクロで引数の数でオーバーロードしたい

#define SELECTER(_1, _2, SELECT, ...) SELECT
#define FOO(...) SELECTER(__VA_ARGS__, NYAN, WAN) (__VA_ARGS__)

FOO(neko)       // expanded to `NYAN (neko)`
FOO(inu, dog)   // expanded to `WAN (inu, dog)`

参考

c - Overloading Macro on Number of Arguments - Stack Overflow

c++ - Can macros be overloaded by number of arguments? - Stack Overflow

RustでArrayにcollectしたい

Rustで以下のコードは違法

(0 .. 9).map(|i| i*i).collect::<[i32; 10]>::()

これは配列がFromIteratorを実装していないため.

こういう時はArrayVecを使う.

取り敢えずCargo.tomldependencies

arrayvec = "0.3.20"

を書き加えて

extern crate arrayvec;
use arrayvec::ArrayVec;

let result: ArrayVec<[_, 10]> = (0..9).map(|i| i*i).collect();
result.into_inner().unwrap()

とすればいい

C++のライブラリをC言語でくるんでRustから使うまで

この記事はAmusementCreatorsAdventCalendar(通称ACAC)の20日目の記事です.

www.adventar.org

にゃん

以前下のような記事を書いたのですが,その時に色々学ぶことがあったので纏めておこうと思います. akitsu-sanae.hatenablog.com

この記事は大まかに分けてC++のコードをCでくるむ部分とそれをRustから使う部分の2つに分かれます.
前半はRustに興味のない人にも役に立つのではと思います.

C++の環境はVisual Studio 2015(v140)
Rustの環境は

  • rustup 0.6.5 (88ef618 2016-11-04)
  • rustc 1.15.0-nightly (2217bd771 2016-11-25) nightly-i686-pc-windows-msvc
  • cargo 0.16.0-nightly (b26d672 2016-11-25)

です.

説明のためライブラリのヘッダファイルはnyan.hpp,ライブラリのバイナリはnyan.libとします.

何か不備などありましたら遠慮なくコメントください.

Visual C++のコードをC言語でくるむ

取り敢えずラップする用のVisual C++プロジェクトを作ります.
ラッパーのcppファイルをwrapper.cppとでもしておきます.
wrapper.cppは基本的に下の様になります.

#include "nyan.hpp"

extern "C" {
    // ここにラップするコードを書いていく
}

フリー関数についてはそのままラップすればよくて特筆すべきことはありません.

例えば下のようなクラスをラップすることを考えます

namespace nyan {

struct Inu {
    explicit Inu() { ... }
    ~Inu() { ... }

    void greet(const char* name) {
        std::cout << "こんにちは!" << name << "さん!wanwan!!" << std::endl;
    }

    ...
};

}

この場合ラップする関数は

  • nyan::Inu::Inu()
  • nyan::Inu::~Inu()
  • nyan::Inu::greet(const char*)

の三つです.
wrapper.cppは下の様になります.

#include "nyan.hpp"

extern "C" {
    typedef struct {
        Inu impl;
    } InuImpl;

    // nyan::Inu::Inu
    InuImpl* nyan_Inu_create() {
        return new InuImpl{};
    }

    // nyan::Inu::~Inu
    void nyan_Inu_delete(InuImpl* inu) {
        delete inu;
    }

    // nyan::Inu::greet
    void nyan_Inu_greet(InuImpl* inu, const char* name) {
        inu->greet(name);
    }
}

まぁつまりC++のクラスであるInuをCのstruct InuImplでラップして,それを経由してコンストラクタやデストラクタ,メンバ関数をラップします.
Inuはほとんどの場合std::shared_ptr<Inu>の形で使われるようならInuImplが持つのはstd::shared_ptr<Inu>でもよいかもしれません.
見てもらえば分かるように,コンストラクタは単純にInuImpl*を返すフリー関数として,デストラクタはInuImpl*を引数に取ってvoidを返すフリー関数としてラップできます.

メンバ関数の扱いなのですが,これはそのクラスのポインタが第一引数になるようなフリー関数としてラップできます.このポインタはC++メンバ関数では暗黙的に渡されているthisポインタのことですね.

こんな感じでバリバリラッパーを書いていきます.

で,当然ですがスタティックリンクライブラリとしてビルドします.
この時,ビルド後イベントにlib /out:lib/nyan-wrapper.lib nyan.lib Release/(プロジェクトの名前).libを付け加えるとwrapper.cppをビルドしたものとnyan.libをくっつけたものをlib/nyan-wrapper.libに吐き出してくれます.
libはLinux環境でのarみたいなものだと思ってくれればよいです.

これでnyan.libをラップしてC言語のインターフェースにしたnyan-wrapper.libが得られました.

C言語インターフェースの静的ライブラリをRustから使う

まずcargo new nyan-rs --binなどとしてRustのプロジェクトをつくります.
取り敢えずsys部分を作っていきます.sysでは静的ライブラリの中にどんな関数があるのかを列挙します.
今回だとsys部分であるnyan-rs/src/sys.rsは下のようになります.

use std::os::raw::c_char;
use std::os::raw::c_int;

pub enum InuImpl {}

#[link(name = "nyan-wrapper")]
extern {
    pub fn nyan_Inu_create() -> *mut InuImpl;
    pub fn nyan_Inu_delete(inu: *mut InuImpl);
    pub fn nyan_Inu_greet(inu: *mut InuImpl, name: *const c_char);
}

今回はstd::os::rawc_charc_intがあったのでそっちを使いましたがlibcを利用した方が良いかもです. InuImplは4行目のようにダミーのenumで宣言してやります.

他にも例えばuser32.libをリンクしたければ

#[link(name = "user32")]
extern {}

などと付け加えます.

これでsys部分は出来ました.
次はsys部分をRustらしくラップしていきます.

src/nyan.rsにこう書きましょう

use std::ffi::CString;

pub struct Inu {
    raw: *mut sys::InuImpl
}

impl Inu {
    pub fn new() -> Self {
        unsafe {
            Inu {
                raw: sys::nyan_Inu_create()
            }
        }
    }

    pub fn greet(&mut self, name: &str) {
        unsafe {
            sys::nyan_Inu_greet(self.raw, CString::new(name).unwrap())
        }
    }
}

impl Drop for Inu {
    fn drop(&mut self) {
        unsafe {
            sys::nyan_Inu_delete(self.raw)
        }
    }
}

これで次からRustのコードでnyan::InuC++nyan::Inuと同じ気持ちで使えるようになります.
やったね!!

参考にしたもの

Foreign Function Interface

Rust と C言語 をコールバックで行き来する(Cブリッジが必要なVer) | d.sunnyone.org

C++14のVariable Templatesの未定義動作かもしれない使い方

この記事はAmusementCreatorsAdventCalendar(通称ACAC)の5日目の記事です.

C++14で追加されたVariable Templatesという機能を使って色々してみたいと思います.
C++14とは2014年に公開された(ことになっている)C++という言語の規格です.

そもそもVariable Templatesとは

Variable Templatesは名の通り変数テンプレートです. 例えば

template<typename T>
constexpr auto PI = static_cast<T>(3.141592);

などとしておくとPI<double>とすればdouble型の円周率,PI<float>とすればfloat型の円周率として使えます.
double一つを引数に持つconstexprなコンストラクタを持つリテラル型Decimalと定義した時,PI<Decimal>と書いてコンパイル時に俺俺クラスの円周率を扱うとかもできます.

まぁC++14以前から使える関数テンプレートを使って

template<typename T>
constexpr auto PI() {
    return static_cast<T>(3.141592);
}

としてPI<double>()などと書いても同じことなのですが括弧が一つ少なくて済んで嬉しいみたいな感じです.

Variable Templatesを使ってフィボナッチ数を求める

こんなコードを書いてみます

#include <iostream>

template<int N>
auto fib = fib<N-1> + fib<N-2>;

template<>
auto fib<0> = 1;

template<>
auto fib<1> = 1;

int main() {
    std::cout << fib<5> << std::endl;
}

g++ 6.1.0でコンパイルして実行すると5番目のフィボナッチ数である8が出力されるはずです!
template引数をコンパイル時に渡される関数の引数とみなすことで変数テンプレートを関数の様に扱っているわけです.
テンプレートの特殊化がパターンみたいに見えて良さそげな気がしないこともありません.

さて,このコードをclang++3.8.0でコンパイルしてみましょう.なぜか0が出力されてませんか?
なんか変数テンプレートを再帰的に使っているのが悪いんですかね….
コンパイラの最適化バグか未定義動作を踏んでいる臭いがプンプンします.

一応このように副作用を生じさせるとclangでも期待通りの結果が得られます.

#include <iostream>

int dummy = 0;

template<int N>
auto fib = (++dummy, fib<N-1> + fib<N-2>);

template<>
auto fib<0> = 1;

template<>
auto fib<1> = 1;

int main() {
    std::cout << fib<5> << std::endl;
}

おまけ

#include<iostream>

template<int x, int y>
auto pixel = (std::cout << "(" << x << "," << y << ")" << std::endl, 1);

template<int width, int height, int x=0, int y=0>
auto image = (image<width/2, height/2, x, y>,
              image<width/2, height/2, x+width/2, y>,
              image<width/2, height/2, x, y+height/2>,
              image<width/2, height/2, x+width/2, y+height/2>);

template<int x, int y>
auto image<0, 0, x, y> = pixel<x, y>;

int main() {
    image<4, 4>;
}

これをコンパイルして実行するとwarningが沢山出てきた後に  { \{ (x, y) \mid 0 \leq x \lt 4 , \space 0 \leq y \lt 4 \} }の各格子点の座標が一つずつ出力されたと思います.
imageのテンプレート引数は左下の座標が (x, y) で横幅width縦幅heightの矩形範囲を表しています.それを左上,右上,左下,右下の四つに分割して再帰的に処理することで,テンプレート再帰深度を横幅と縦幅の積に対する対数に保っています.
そしてwidthとheightがともに0であればそれはつまり点なので変数テンプレートpixelに渡してpixelで出力するものを決めています.
auto pixel = ...の部分をいい感じにして4よりもっと大きな数をしていすれば変数テンプレートでレイトレーシングとか出来るんじゃないかなぁと思っているけど時間が超かかる上にメモリバカ食いしそう…

最後に

良い子のみんなはマネしないように!!

RustからAltseedを使う

これはAmusementCreators AdventCalendar(通称ACAC)の12日目の記事です.

AltseedというC++/C#/Java製のゲームエンジンがあるのですが,それをRustから使ってみたという内容です.

VisualC++などの扱いには慣れていないので何か変なことを言っていたら指摘していただけると助かります.

Altseedとは

AltseedはAmusementCreatorsのOBやその他にも色々な人が集まって作っているゲームエンジンです.
AmusementCreatorsの作品はこのゲームエンジンを利用したものがほとんどです.
僕が説明するよりも ゲームエンジン - Altseed - などを見たほうが早いでしょう.

Rustとは

RustはFireFoxThunderbirdでおなじみのMozzilaが開発している言語です.
C++と同じくゼロオーバーヘッドを原則としている言語で,特徴はlifetimeによるdatarace安全でしょうか.
C++OCamlを足して2で割ったというイメージを持ちますがHaskellのほうにも結構引きずられている気がします.

公式ページは下です. www.rust-lang.org

もし興味があって使ってみたければrustupからインストールすると良さそげです. www.rustup.rs

RustからAltseedを使う仕組み

RustはFFIにも力をいれていてC言語の関数をかなり楽に呼び出すことができます.
なので,C++版のAltseedをC言語のインターフェースでくるんでやって,それをRustから使ってやります.

注意

本記事では僕の書いたasd-c-wrapperとかaltseed-rsを使っています.
それらは開発が始まった段階であり,この記事が公開されてからいくらか経つと記事を書いている現在と違ったものになっているでしょう.
RustでAltseedを使いたいならこの記事を読むよりもgithub上に上がっているReadmeなりを読んだ方がよいです.

実際にやっていく

環境はVisual Studio 2015とrustupを想定しています.

まずAltseedのCラッパーを準備します
git clone https://github.com/akitsu-sanae/asd-c-wrapper
として中のasd-c-wrapper.slnをVisual Studio 2015で開いてください.
Release/x86モードになっていることを確認してビルドするとlib以下にasd-c.libが生成されていると思います.
失敗した?それは辛いですね

次に作りたいゲームのRustのプロジェクトを作って色々設定します.
cargo new nyao --binとしてください(nyaoの部分は作りたいゲームの名前を入れていください)
プロジェクトの中にCargo.tomlができているので,その[dependencies]の下に
altseed = { git = "https://github.com/akitsu-sanae/altseed-rs"}
と追加してください.

Altseed_core.dllと先ほど生成されたasd-c.libをプロジェクトディレクトリに入れます.

rustcをlibファイルと対応しているものにします.
つまりrustup default nightly-i686-pc-windows-msvc としてください.

src/main.rsを編集してゲームのコードを書いていきます. 取り敢えず

extern crate altseed as asd;

fn main() {
    asd::initialize("test", 640, 480);
    let mut obj = asd::TextureObject2D::new();
    obj.texture(&mut asd::Texture2D::new("image.png"));
    asd::add_object2d(&mut obj);
    while asd::do_events() {
        asd::update();
    }
    asd::terminate();
}

とでも書いておきましょう.

これはimage.pngという画像を表示するというプログラムなので適当にimage.pngという画像を作ってプロジェクトのディレクトリに入れときます.

cargo runします.image.pngが左上に表示されているウィンドウが出たら成功です!!!

参考にしたもの

Foreign Function Interface

Rust言語でDxLibを使う - しらいとブログ

Rust と C言語 をコールバックで行き来する(Cブリッジが必要なVer) | d.sunnyone.org