~otheb/bare

5e3ba4ff3a6d11217b57e9fdc3e2241203770793 — Olie Ayre 4 months ago
Initial commit

All methods for reading and writing all types in the spec have an
initial (and likely very buggy) implementation.

Note that because of the way D works, some features are not directly
accessible e.g. as there are no built-in varint types, they have
distinct methods and must be called explicitly otherwise data will be
read/written as a ulong/long. Read/write for full-featured types will
come later with schema-based templates.
4 files changed, 206 insertions(+), 0 deletions(-)

A .gitignore
A bare.d
A dub.sdl
A main.d
A  => .gitignore +2 -0
@@ 1,2 @@
bare
.dub

A  => bare.d +170 -0
@@ 1,170 @@
module bare ;

import std.algorithm ;
import std.bitmanip  ;
import std.range     ;
import std.conv      ;
import std.typecons  ;
import std.traits    ;
import std.variant   ;

// PRIMITIVE TYPES

// u*, i*, f*
static foreach ( t ; [ "ubyte" , "ushort" , "uint" , "ulong" , "byte" ,
                       "short" , "int" , "long" , "float" , "double" ] ) {
	T readb(T)( ref ubyte[] data ) if ( is( T == mixin( t ) ) )
			in ( data.length >= T.sizeof , "Not enough data." ) {
		return data.read!( T , Endian.littleEndian ) ;
	}
	ubyte[] writeb(T)( T v ) if ( is( T == mixin( t ) ) ) {
		ubyte[] buffer = [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ;
		buffer.write!( T , Endian.littleEndian )( v , 0 ) ;
		return buffer[ 0 .. T.sizeof ] ;
	}
}

// bool
bool readb(T)( ref ubyte[] data ) if ( is( T == bool ) )
		in ( data.length >= 1 , "Not enough data." ) {
	scope(exit) data.popFront ;
	return data[0] > 0 ;
}
ubyte[] writeb( bool v ) { return v ? [ ubyte( 1 ) ] : [ ubyte( 0 ) ] ; }

// uint
ulong rduint( ref ubyte[] data ) {
	ulong v = 0 ;
	ulong s = 0 ;
	while ( true ) {
		ubyte b = data[0] ; data.popFront ;
		v |= cast(ulong)( b & 127 ) << s ;
		s += 7 ;
		if ( b < 128 ) break ;
	}
	return v ;
}
ubyte[] wruint( ulong v ) {
	ubyte[] data = [] ;
	while ( v > 0 ) {
		data ~= 0x80 + ( v & 0b01111111 ) ;
		v >>>= 7 ;
	}
	data[ $ - 1 ] -= 0x80 ;
	return data ;
}

// int
long rdint( ref ubyte[] data ) {
	ulong v = data.rduint ;
	return ( v >>> 1 ) ^ - ( v & 1 ) ;
}
ubyte[] wrint( long v ) {
	return wruint( cast(ulong)( ( v << 1 ) ^ ( v >> 63 ) ) ) ;
}

// string
string readb(T)( ref ubyte[] data ) if ( is( T == string ) ) {
	ulong l = data.rduint ;
	assert( data.length >= l , "Not enough data." ) ;
	scope(exit) data.popFrontExactly( l ) ;
	return cast(string)( data[0..l] ) ;
}
ubyte[] writeb( string v ) { return wruint( v.length ) ~ cast(ubyte[])v ; }

// void
void readb(T)( ref ubyte[] data ) if ( is( T == void ) ) { return ; }
// no write as it is useless

// NOTE: use *uint methods for enums

// NOTE: data and data<length> use array aggregate methods

// AGGREGATE TYPES

// optional<type>
T readb(T)( ref ubyte[] data )
		if ( __traits( isSame , TemplateOf!T , Nullable ) ) {
	return data.readb!bool
		? mixin( "T( readb!" , TemplateArgsOf!(T)[0] , "( data ) )" )
		: T() ;
}
ubyte[] writeb(T)( Nullable!T v ) {
	return v.isNull
		? [ ubyte( 0 ) ]
		: [ ubyte( 1 ) ] ~ writeb( v.get ) ;
}

// [length]type
T readb(T)( ref ubyte[] data ) if ( __traits( isStaticArray , T ) ) {
	T result ;
	foreach ( i ; 0 .. result.length )
		result[l] = mixin( "readb!" , ElementType!T , "( data )" ) ;
	return result ;
}
ubyte[] writeb(T)( T v ) if ( __traits( isStaticArray , T ) ) {
	return v.map!( i => writeb( i ) ).array.join() ;
}

// []type
T readb(T)( ref ubyte[] data ) if ( isDynamicArray!T && ! is( T == string ) ) {
	T[] result = [] ;
	foreach ( i ; 0 .. data.rduint )
		result ~= mixin( "readb!" , ElementType!T , "( data )" ) ;
	return result ;
}
ubyte[] writeb(T)( T v ) if ( isDynamicArray!T && ! is( T == string ) ) {
	return wruint( v.length ) ~ v.map!( i => writeb( i ) ).array.join() ;
}

// map[type A]type B
T readb(T)( ref ubyte[] data ) if ( isAssociativeArray!T ) {
	T result ;
	foreach ( i ; 0 .. data.rduint )
		result[ mixin( "readb!" , KeyType!T , "( data )" ) ]
			= mixin( "readb!" , ElementType!T , "( data )" ) ;
	return result ;
}
ubyte[] writeb(T)( T v ) if ( isAssociativeArray!T ) {
	return wruint( v.keys.length )
	       ~ v.keys.map!( k => writeb( k ) ~ writeb( v[k] ) ).array.join() ;
}

// (type | type | ...)
T readb(T)( ref ubyte[] data )
		if ( __traits( isSame , TemplateOf!T , Algebraic ) ) {
	ulong t = data.rduint ;
	static foreach ( i , V ; TemplateArgsOf!T )
		if ( i == t ) return mixin( "readb!" , V , "( data )" ) ;
}
ubyte[] writeb(T)( T v )
		if ( __traits( isSame , TemplateOf!T , Algebraic ) ) {
	static foreach ( i , V ; TemplateArgsOf!T )
		if ( v.type == typeid(V) )
			return wruint( i ) ~ write( v.get!V ) ;
	assert( 0 ) ;
}

// struct
T readb(T)( ref ubyte[] data )
		if ( isAggregateType!T
		     && ! __traits( isSame , TemplateOf!T , Nullable )
		     && ! __traits( isSame , TemplateOf!T , Algebraic ) ) {
	alias names = FieldNameTuple!T ;
	alias types = Fields!T ;
	static assert( names.length == types.length ) ;
	T result ;
	static foreach ( i ; 0 .. names.length )
		mixin( "result." , names[i] , " = readb!" , types[i] , "( data ) ;" ) ;
	return result ;
}
ubyte[] writeb(T)( T v )
		if ( isAggregateType!T
			 && ! __traits( isSame , TemplateOf!T , Nullable )
			 && ! __traits( isSame , TemplateOf!T , Algebraic ) ) {
	alias names = FieldNameTuple!T ;
	ubyte[] result ;
	static foreach ( i ; 0 .. names.length )
		result ~= writeb( mixin( "v." , names[i] ) ) ;
	return result ;
}

A  => dub.sdl +8 -0
@@ 1,8 @@
name "bare"
description "BARE implementation"
authors "Olie Ayre"
license "GPL-3.0"
homepage "https://git.sr.ht/~otheb/bare"
sourcePaths "."
importPaths "."
targetType "executable"

A  => main.d +26 -0
@@ 1,26 @@
import bare ;

import std.stdio ;

import std.typecons ;

struct NamedPoint {
	uint   x    ;
	uint   y    ;
	string name ;

	this( uint x_ , uint y_ , string n ) {
		x    = x_ ;
		y    = y_ ;
		name = n  ;
	}
}

void main() {
	auto a = writeb( Nullable!int( 1234 ) ) ;
	readb!( Nullable!int )( a ).writeln() ;

	auto p = NamedPoint( 123 , 456 , "descriptive name" ) ;
	auto d = writeb( p ) ;
	readb!NamedPoint( d ).writeln() ;
}