s7に独自の型を追加する

#はじめに 

s7に独自の型を追加しようと思ったところ、マニュアルの内容が古く、掲載されているサンプルそのままではコンパイルできなかったので、補完する意味で簡単に説明したい。

なお、俺が過去に投稿したs7wrapを使用する。

https://qiita.com/ossan-buhibuhi/items/98520f3adb1c70cb0475
 
#正規表現オブジェクト

例として、std::regexをs7から使えるようにしてみたい。ファイル構成は以下のようになる。

	api.hpp ... s7wrapが出力するヘッダーファイル
	api.cpp ... api.hppに対応するソースファイル
	api_regex.hpp ... 正規表現オブジェクトのためのヘッダーファイル
	api_regex.cpp ... api_regex.hppに対応するソースファイル
	main.cpp

s7wrapに渡すAPI定義ファイルのapi_regex.defは、

```scheme
; api_regex.def

(regex atom "正規表現オブジェクトを作成する。"
  (pattern str "正規表現のパターン")
  (callback atom "マッチ/サブマッチの文字列とアキュムレータを引数に取り、新たなアキュムレータを返す。"))

(is_regex bool "objが正規表現オブジェクトなら真を返す。"
  (obj atom "対象オブジェクト"))

(regex__match atom "strが正規表現にマッチしたら更新されたアキュムレータを返す。"
  (reg atom "正規表現オブジェクト")
  (str str "対象文字列")
  (acc atom "アキュムレータの初期値"))
 ```

コールバック関数とアキュムレータは正規表現オブジェクトにs7のオブジェクトを保持させるために無理やり考えたw

これを

	cat api.def | ./s7 s7wrap.scm > api.hpp

として、ヘッダーファイルを作成。

このAPIだけではs7は独自の型として認識してくれないので、追加の実装が必要となる。

```cpp
// api_regex.hpp

#ifndef API_REGEX_HPP
#define API_REGEX_HPP

#include <string>
#include <regex>
#include "api.hpp"

class ApiRegex : public std::regex {
	std::string Pattern;
	s7_pointer Callback;
public:
	ApiRegex (const std::string pattern, s7_pointer callback)
	: std::regex (pattern), Pattern (pattern), Callback (callback) {}

	const std::string GetPattern() { return Pattern; }
	s7_pointer GetCallback() { return Callback; }
};

extern int api_regex_type_tag;
void api_regex__init();

extern "C" {
s7_pointer regex_to_string (s7_scheme *sc, s7_pointer args);
void free_regex (void *value);
void mark_regex (void *value);
bool regex_is_equal (void *value1, void *value2);
}

#endif
```

std::regexを継承しているのはどうかなと思うw

```cpp
// api_regex.cpp

#include "api_regex.hpp"
#include <regex>

int api_regex_type_tag = 0;

void api_regex__init()
{
	S7& s7 = S7::get();
	api_regex_type_tag = s7_make_c_type (s7, "api_regex");
	s7_c_type_set_free (s7, api_regex_type_tag, free_regex);
	s7_c_type_set_mark (s7, api_regex_type_tag, mark_regex);
	s7_c_type_set_equal (s7, api_regex_type_tag, regex_is_equal);
	s7_c_type_set_to_string (s7, api_regex_type_tag, regex_to_string);
}

s7_pointer S7::api_regex (const char* pattern, s7_pointer callback)
{
	ApiRegex* reg = new ApiRegex (pattern, callback);
	return (s7_make_c_object (s7, api_regex_type_tag, (void *)reg));
}


bool S7::api_is_regex (s7_pointer obj)
{
	return (s7_is_c_object (obj) &&
			s7_c_object_type (obj) == api_regex_type_tag);
}

s7_pointer S7::api_regex__match (s7_pointer _reg, const char* str, s7_pointer acc)
{
	ApiRegex* reg = (ApiRegex*)s7_c_object_value (_reg);
	s7_pointer callback = reg->GetCallback();
	std::cmatch match;
	std::regex_search (str, match, *reg);
	for (std::csub_match submatch : match)
	{
		s7_pointer str = s7_make_string (s7, submatch.str().c_str());
		s7_pointer args = s7_list_nl (s7, 2, str, acc, NULL);
		acc = s7_call (s7, callback, args);
	}
	return acc;
}

extern "C" {

s7_pointer regex_to_string(s7_scheme *sc, s7_pointer args)
{
	ApiRegex* reg = (ApiRegex*)s7_c_object_value (s7_car (args));
	std::string str = "<api_regex \"" + reg->GetPattern() + "\">";
	return s7_make_string (sc, str.c_str());
}

void free_regex (void *value)
{
	ApiRegex* reg = (ApiRegex*)value;
	delete reg;
}

void mark_regex (void *value)
{
	ApiRegex* reg = (ApiRegex*)value;
	s7_mark (reg->GetCallback());
}

bool regex_is_equal (void *value1, void *value2)
{
	ApiRegex* reg1 = (ApiRegex *)value1;
	ApiRegex* reg2 = (ApiRegex *)value2;
	return (reg1->GetPattern() == reg2->GetPattern());
}

} // extern "C"
```

s7_markってのはマークアンドスウィープのマークで、対象オブジェクトがガベージコレクトされないようにする。

```cpp
// api.cpp

#include "api.hpp"
#include "api_regex.hpp"

void S7::init()
{
	api_regex__init();
}
```

main.cppは使い回しできるようにしてある。

```cpp
// main.cpp

#include <iostream>
#include "api.hpp"

int main (int argc, char** argv)
{
	if (argc < 2) {
		std::cerr << "need .scm file." << std::endl;
		return 1;
	}
	S7& s7 = S7::get();
	s7.init();
	const char* path = argv [1];
	s7.load (path);
	return 0;
}
```

呼び出し例は、

```scheme
; test.scm
(let ((reg (api_regex "([0-9]{4})/([0-9]{2})/([0-9]{2})" cons)))
  (write (reverse (api_regex__match reg "2021/03/16" '())))
  (newline))
```

	> ./s7_regex test.scm
	("2021/03/16" "2021" "03" "16")

結構面倒に思えるかもしれないけど、以上をテンプレとして使ってもらえればほとんど置換作業だけで済むはず。
 
#可搬性 

上記の正規表現オブジェクトには可搬性があり、手軽に別のアプリケーションに組み込める。

まず以下のファイルをプロジェクトのディレクトリにコピーする。

	api_regex.def
	api_regex.hpp
	api_regex.cpp

で、s7wrapにAPI定義ファイルを渡す時に以下のようにしてプロジェクトのAPI定義ファイルとマージする。

	cat api.def api_regex.def | ./s7 s7wrap.scm > api.hpp

あとはプロジェクトのS7::init()の中でapi_regex__init()を呼び出せばおしまい。
 
#おわりに 

単なる備忘録になってしまった。丁寧に解説できなくてごめんね。

いじょう