1 module nxt.packed_string;
2 
3 /** String packed into one word.
4  *
5  * Length is stored in upper `lengthBits` of pointer/word `_raw`.
6  *
7  * All length bits set means memory bits of `_raw` points to `string`. This
8  * makes `PackedString` `nothrow`.
9  *
10  * TODO: If D's GC doesn't ignore the upper `lengthBits` bits of pointer, make
11  * `core.memory.GC` be told that every `PackedString` should be bit-anded with
12  * `addressMask` before scanned for an address. Related functions:
13  * - isLarge.
14  *
15  * Proposed API for specifying this is: `__traits(adressBitMask, declaration,
16  * mask)` where `declaration.sizeof == size_t.sizeof`.
17  */
18 @safe struct PackedString {
19 	enum totalBits = 8 * _raw.sizeof;
20 
21 	/++ Number of bits used to store length.
22 		2024-01-01: Address space of a single Linux user process is 47 bits.
23 	 +/
24 	enum lengthBits = 16;
25 
26 	/// Number of bits used to memory address.
27 	enum addressBits = totalBits - lengthBits;
28 
29 	/// Capacity of small variant where `length` fits in `lengthBits`.
30 	public enum smallCapacity = (2 ^^ lengthBits - 1) - 1;
31 
32 	/// Bit mask of length part.
33 	enum size_t lengthMask = (cast(size_t)(2^^lengthBits - 1)) << addressBits;
34 
35 	/// Bit mask of address part.
36 	enum size_t addressMask = ~lengthMask;
37 
38 	alias Large = immutable(char)[];
39 
40 pure nothrow @nogc:
41 
42 	this(in string x) @trusted in(!((cast(size_t)x.ptr) & lengthMask)) {
43 		if (x.length <= smallCapacity)
44 			_raw = cast(size_t)(x.ptr) | (x.length << addressBits);
45 		else {
46 			assert(0, "TODO: implement this");
47 			// string* y = new string; /+ TODO: how do I do this? +/
48 			// *y = x;
49 			// _raw = cast(size_t)(x.ptr) | (x.length << addressBits);
50 		}
51 	}
52 
53 	/** Returns: `true` iff this is a large string, otherwise `false.` */
54 	@property bool isLarge() const scope @trusted
55 	{
56 		version (D_Coverage) {} else pragma(inline, true);
57 		return (_raw & lengthMask) == (2^^lengthBits) - 1;
58 	}
59 
60 	/// Get pointer to characters.
61 	immutable(char)* ptr() const @property @trusted
62 		=> cast(typeof(return))(_raw & addressMask);
63 
64 	/// Get length.
65 	size_t length() const @property @safe => (cast(size_t)_raw) >> addressBits;
66 
67 	/// Get slice.
68 	string opSlice() const @property @trusted => ptr[0 .. length];
69 
70 	void toString(Sink)(ref scope Sink sink) const @property scope => sink(opSlice());
71 
72 	alias opSlice this;
73 
74 	private size_t _raw;
75 }
76 
77 version (unittest) {
78 	static assert(PackedString.sizeof == size_t.sizeof);
79 	static assert(PackedString.totalBits == 64);
80 	static assert(PackedString.addressBits == 48);
81 	static assert(PackedString.smallCapacity == 65534);
82 	static assert(PackedString.lengthMask == 0xffff_0000_0000_0000);
83 	static assert(PackedString.addressMask == 0x0000_ffff_ffff_ffff);
84 }
85 
86 ///
87 pure @safe unittest {
88 	const s = "alpha";
89 	PackedString p = s;
90 	assert(p.ptr == s.ptr);
91 	assert(p.length == s.length);
92 	assert(p[] == s);
93 	assert(p == s);
94 }
95 
96 ///
97 pure @safe unittest {
98 	string s;
99 	s.length = PackedString.smallCapacity;
100 	PackedString p = s;
101 	assert(p.ptr == s.ptr);
102 	assert(p.length == s.length);
103 	assert(p[] == s);
104 	assert(p == s);
105 	assert(p is s);
106 }