D言語でLZMA圧縮/伸展する

タイトルママです。
過去のソースコードから発掘したので紹介。

ベースはdeimosの[liblzma](https://github.com/D-Programming-Deimos/liblzma)

出力Rangeベースにラッピングしました。

```d
pragma(lib, "lzma");
pragma(lib, "liblzma");

import deimos.lzma;
import std.array, std.exception;

/*******************************************************************************
 * エンコーダー
 */
struct LzmaStreamEncoder
{
private:
	lzma_stream _strm = lzma_stream.init; /* alloc and init lzma_stream struct */
	Appender!(ubyte[]) _app;
	
public:
	/***************************************************************************
	 * 設定
	 */
	void reset(uint preset = 6 | LZMA_PRESET_EXTREME, lzma_check check = lzma_check.LZMA_CHECK_CRC64)
	{
		if (_strm != lzma_stream.init)
		{
			lzma_end(&_strm);
			_strm = lzma_stream.init;
		}
		_app.shrinkTo(0);
		enforce(lzma_easy_encoder(&_strm, preset, check) == lzma_ret.LZMA_OK);
	}
	
	~this()
	{
		lzma_end(&_strm);
	}
	
	/***************************************************************************
	 * データ追加
	 */
	void put(in ubyte[] buf)
	{
		if (_strm is lzma_stream.init)
			reset();
		ubyte[1024] outbuf = void;
		_strm.next_in   = buf.ptr;
		_strm.avail_in  = buf.length;
		
		while (1)
		{
			_strm.next_out  = outbuf.ptr;
			_strm.avail_out = outbuf.length;
			/* compress data */
			auto ret = lzma_code(&_strm, lzma_action.LZMA_RUN);
			enforce((ret == lzma_ret.LZMA_OK) || (ret == lzma_ret.LZMA_STREAM_END));
			
			auto len = outbuf.length - _strm.avail_out;
			if (len == 0)
			{
				break;
			}
			_app.put(outbuf[0..len]);
			if ( _strm.avail_out != 0 )
			{
				break;
			}
		}
	}
	
	/***************************************************************************
	 * 圧縮されたデータを取得
	 */
	ubyte[] complete()
	{
		ubyte[1024] outbuf = void;
		_strm.next_in   = null;
		_strm.avail_in  = 0;
		while (1)
		{
			_strm.next_out  = outbuf.ptr;
			_strm.avail_out = outbuf.length;
			/* compress data */
			auto ret = lzma_code(&_strm, lzma_action.LZMA_FINISH);
			enforce((ret == lzma_ret.LZMA_OK) || (ret == lzma_ret.LZMA_STREAM_END));
			
			auto len = outbuf.length - _strm.avail_out;
			if (len == 0)
			{
				break;
			}
			_app.put(outbuf[0..len]);
			if ( _strm.avail_out != 0 )
			{
				break;
			}
		}
		return _app.data();
	}
}


/*******************************************************************************
 * デコーダー
 */
struct LzmaStreamDecoder
{
private:
	lzma_stream _strm = lzma_stream.init; /* alloc and init lzma_stream struct */
	Appender!(ubyte[]) _app;
	
public:
	/***************************************************************************
	 * 設定
	 */
	void reset(ulong memlimit = lzma_easy_decoder_memusage(9))
	{
		if (_strm != lzma_stream.init)
		{
			lzma_end(&_strm);
			_strm = lzma_stream.init;
		}
		_app.clear();
		enforce(lzma_stream_decoder(&_strm, memlimit, 0) == lzma_ret.LZMA_OK);
	}
	
	~this()
	{
		lzma_end(&_strm);
	}
	
	
	/***************************************************************************
	 * 圧縮されたデータを追加
	 */
	void put(in ubyte[] buf)
	{
		if (_strm is lzma_stream.init)
			reset();
		ubyte[1024] outbuf = void;
		_strm.next_in   = buf.ptr;
		_strm.avail_in  = buf.length;
		while (1)
		{
			_strm.next_out  = outbuf.ptr;
			_strm.avail_out = outbuf.length;
			/* compress data */
			auto ret = lzma_code(&_strm, lzma_action.LZMA_RUN);
			enforce((ret == lzma_ret.LZMA_OK) || (ret == lzma_ret.LZMA_STREAM_END));
			
			auto len = outbuf.length - _strm.avail_out;
			if (len == 0)
			{
				break;
			}
			_app.put(outbuf[0..len]);
			if ( _strm.avail_out != 0 )
			{
				break;
			}
		}
	}
	
	
	/***************************************************************************
	 * 進展されたデータを取得
	 */
	@property ubyte[] get()
	{
		scope (exit) _app.clear();
		return _app.data();
	}
}


unittest
{
	LzmaStreamEncoder enc;
	LzmaStreamDecoder dec;
	ubyte[1024] buf;
	
	// 単純なデータで圧縮のテスト
	buf[] = 1;
	enc.put(buf);
	buf[] = 2;
	enc.put(buf);
	buf[] = 3;
	enc.put(buf);
	buf[] = 4;
	enc.put(buf);
	auto compressed = enc.complete();
	// 圧縮されてる
	assert(compressed.length < 1024 * 4);
	
	// 伸展のテスト
	dec.put(compressed);
	auto decompressed = dec.get();
	assert(decompressed.length == 1024 * 4);
	
	import std.algorithm, std.range;
	assert(equal(decompressed[1024*0 .. 1024*1], repeat(1, 1024)));
	assert(equal(decompressed[1024*1 .. 1024*2], repeat(2, 1024)));
	assert(equal(decompressed[1024*2 .. 1024*3], repeat(3, 1024)));
	assert(equal(decompressed[1024*3 .. 1024*4], repeat(4, 1024)));
}
```

特別説明することはありませんね……
dub使えば先頭のpragma(lib, ...)は要らないかも？