12 Commits

Author SHA1 Message Date
c1864cf473 Add dynamic library target 2018-01-13 06:21:42 +01:00
8db1851c5c Update dmd to 2.078.0 2018-01-04 05:36:46 +01:00
12de700706 Fix formatting null class references 2017-12-16 09:42:57 +01:00
78a8afdf75 Format stringish ranges 2017-12-15 22:42:18 +01:00
3c996d7c57 Add struct formatting 2017-12-14 19:47:13 +01:00
2a68048fc1 Put real formatting code into a separate function 2017-12-09 10:02:54 +01:00
907f7a4e61 Remove IO branch 2017-12-09 09:53:23 +01:00
670328c047 Drop support for 2.075.1 2017-12-08 10:58:39 +01:00
7fe69ccc5c format: Aggregate types 2017-12-08 10:56:59 +01:00
26c3532e28 Wrap formatting into printToString
printToString gets the output string as argument and can be called
recursive with the same output string to format ranges.
2017-12-03 19:53:06 +01:00
75ce854192 Support dmd 2.077.1 2017-12-02 10:40:40 +01:00
9e16d84f9e Reintroduce isStruct, isClass and isInterface
since they can be useful for generic programming.
2017-11-29 19:53:28 +01:00
8 changed files with 381 additions and 212 deletions

View File

@ -7,9 +7,9 @@ os:
language: d language: d
d: d:
- dmd-2.077.0 - dmd-2.078.0
- dmd-2.077.1
- dmd-2.076.1 - dmd-2.076.1
- dmd-2.075.1
env: env:
matrix: matrix:
@ -22,7 +22,7 @@ addons:
- gcc-multilib - gcc-multilib
before_script: before_script:
- if [ "$PS1" = '(dmd-2.077.0)' ]; then - if [ "$PS1" = '(dmd-2.078.0)' ]; then
export UNITTEST="unittest-cov"; export UNITTEST="unittest-cov";
fi fi

View File

@ -12,8 +12,8 @@ Tanya is a general purpose library for D programming language.
Its aim is to simplify the manual memory management in D and to provide a Its aim is to simplify the manual memory management in D and to provide a
guarantee with @nogc attribute that there are no hidden allocations on the guarantee with @nogc attribute that there are no hidden allocations on the
Garbage Collector heap. Everything in the library is usable in @nogc code. Garbage Collector heap. Everything in the library is usable in @nogc code.
Tanya extends Phobos functionality and provides alternative implementations for Tanya provides data structures and utilities to facilitate painless systems
data structures and utilities that depend on the Garbage Collector in Phobos. programming in D.
* [API Documentation](https://docs.caraus.io/tanya) * [API Documentation](https://docs.caraus.io/tanya)
* [Contribution guidelines](CONTRIBUTING.md) * [Contribution guidelines](CONTRIBUTING.md)
@ -149,9 +149,9 @@ There are more containers in the `tanya.container` package.
| DMD | GCC | | DMD | GCC |
|:-------:|:--------------:| |:-------:|:--------------:|
| 2.077.0 | *gdc-5* branch | | 2.078.0 | *gdc-5* branch |
| 2.077.1 | |
| 2.076.1 | | | 2.076.1 | |
| 2.075.1 | |
### Current status ### Current status
@ -161,13 +161,10 @@ Following modules are under development:
|----------|:---------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |----------|:---------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| BitArray | bitvector | [![bitvector](https://travis-ci.org/caraus-ecms/tanya.svg?branch=bitvector)](https://travis-ci.org/caraus-ecms/tanya) [![bitvector](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/bitvector?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/bitvector) | | BitArray | bitvector | [![bitvector](https://travis-ci.org/caraus-ecms/tanya.svg?branch=bitvector)](https://travis-ci.org/caraus-ecms/tanya) [![bitvector](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/bitvector?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/bitvector) |
| TLS | crypto | [![crypto](https://travis-ci.org/caraus-ecms/tanya.svg?branch=crypto)](https://travis-ci.org/caraus-ecms/tanya) [![crypto](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/crypto?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/crypto) | | TLS | crypto | [![crypto](https://travis-ci.org/caraus-ecms/tanya.svg?branch=crypto)](https://travis-ci.org/caraus-ecms/tanya) [![crypto](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/crypto?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/crypto) |
| File IO | io | [![io](https://travis-ci.org/caraus-ecms/tanya.svg?branch=io)](https://travis-ci.org/caraus-ecms/tanya) [![io](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/io?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/io) |
### Release management ### Release management
3-week release cycle. Deprecated features are removed after one release that includes these deprecations.
Deprecated features are removed after one release (in approximately 6 weeks after deprecating).
## Further characteristics ## Further characteristics

View File

@ -4,10 +4,16 @@ os: Visual Studio 2015
environment: environment:
matrix: matrix:
- DC: dmd - DC: dmd
DVersion: 2.077.0 DVersion: 2.078.0
arch: x64 arch: x64
- DC: dmd - DC: dmd
DVersion: 2.077.0 DVersion: 2.078.0
arch: x86
- DC: dmd
DVersion: 2.077.1
arch: x64
- DC: dmd
DVersion: 2.077.1
arch: x86 arch: x86
- DC: dmd - DC: dmd
DVersion: 2.076.1 DVersion: 2.076.1
@ -15,12 +21,6 @@ environment:
- DC: dmd - DC: dmd
DVersion: 2.076.1 DVersion: 2.076.1
arch: x86 arch: x86
- DC: dmd
DVersion: 2.075.1
arch: x64
- DC: dmd
DVersion: 2.075.1
arch: x86
skip_tags: true skip_tags: true

View File

@ -12,7 +12,12 @@
"configurations": [ "configurations": [
{ {
"name": "library", "name": "library",
"targetType": "library", "targetType": "staticLibrary",
"versions": ["TanyaPhobos"]
},
{
"name": "dynamic",
"targetType": "dynamicLibrary",
"versions": ["TanyaPhobos"] "versions": ["TanyaPhobos"]
}, },
{ {

View File

@ -264,7 +264,8 @@ if (is(Unqual!E == char))
body body
{ {
dchar chr; dchar chr;
ubyte units, mask; ubyte units;
int mask;
const(char)* it = this.begin; const(char)* it = this.begin;
if (*it & 0x80) if (*it & 0x80)

View File

@ -323,9 +323,9 @@ private HP raise2Power10(const HP value, int power)
} }
/* /*
* Given a float value, returns the significant bits in bits, and the position * Given a float value, returns the significant bits, and the position of the
* of the decimal point in $(D_PARAM exponent). +/-Inf and NaN are specified * decimal point in $(D_PARAM exponent). +/-Inf and NaN are specified by
* by special values returned in the $(D_PARAM exponent). Sing bit is set in * special values returned in the $(D_PARAM exponent). Sing bit is set in
* $(D_PARAM sign). * $(D_PARAM sign).
*/ */
private const(char)[] real2String(double value, private const(char)[] real2String(double value,
@ -505,86 +505,120 @@ if (T.sizeof == U.sizeof)
copy((&src)[0 .. 1], (&dest)[0 .. 1]); copy((&src)[0 .. 1], (&dest)[0 .. 1]);
} }
package(tanya) String format(string fmt, Args...)(auto ref Args args) private void formatReal(T)(ref T arg, ref String result)
if (isFloatingPoint!T)
{ {
String result; char[512] buffer; // Big enough for e+308 or e-307.
char[8] tail = 0;
char[] bufferSlice = buffer[64 .. $];
uint precision = 6;
bool negative;
int decimalPoint;
static if (is(Unqual!(Args[0]) == typeof(null))) // Read the double into a string.
auto realString = real2String(arg, buffer, decimalPoint, negative);
auto length = cast(uint) realString.length;
// Clamp the precision and delete extra zeros after clamp.
uint n = precision;
if (length > precision)
{ {
result.insertBack("null"); length = precision;
} }
else static if(is(Unqual!(Args[0]) == bool)) while ((length > 1) && (precision != 0) && (realString[length - 1] == '0'))
{ {
result.insertBack(args[0] ? "true" : "false"); --precision;
--length;
} }
else static if (isSomeString!(Args[0])) // String
if (negative)
{ {
if (args[0] is null) result.insertBack('-');
}
if (decimalPoint == special)
{
result.insertBack(realString);
return;
}
// Should we use sceintific notation?
if ((decimalPoint <= -4) || (decimalPoint > cast(int) n))
{
if (precision > length)
{ {
result.insertBack("null"); precision = length - 1;
}
else if (precision > 0)
{
// When using scientific notation, there is one digit before the
// decimal.
--precision;
}
// Handle leading chars.
bufferSlice.front = realString[0];
bufferSlice.popFront();
if (precision != 0)
{
bufferSlice.front = period;
bufferSlice.popFront();
}
// Handle after decimal.
if ((length - 1) > precision)
{
length = precision + 1;
}
realString[1 .. length].copy(bufferSlice);
bufferSlice.popFrontExactly(length - 1);
// Dump the exponent.
tail[1] = 'e';
--decimalPoint;
if (decimalPoint < 0)
{
tail[2] = '-';
decimalPoint = -decimalPoint;
} }
else else
{ {
result.insertBack(args[0]); tail[2] = '+';
}
n = decimalPoint >= 100 ? 5 : 4;
tail[0] = cast(char) n;
while (true)
{
tail[n] = '0' + decimalPoint % 10;
if (n <= 3)
{
break;
}
--n;
decimalPoint /= 10;
} }
} }
else static if (isSomeChar!(Args[0])) // Char else
{ {
result.insertBack(args[0]); if (decimalPoint > 0)
}
else static if (isFloatingPoint!(Args[0])) // Float
{
char[512] buffer; // Big enough for e+308 or e-307.
char[8] tail = 0;
char[] bufferSlice = buffer[64 .. $];
uint precision = 6;
bool negative;
int decimalPoint;
// Read the double into a string.
auto realString = real2String(args[0], buffer, decimalPoint, negative);
auto length = cast(uint) realString.length;
// Clamp the precision and delete extra zeros after clamp.
uint n = precision;
if (length > precision)
{ {
length = precision; precision = decimalPoint < (cast(int) length)
? length - decimalPoint
: 0;
} }
while ((length > 1) else
&& (precision != 0)
&& (realString[length - 1] == '0'))
{ {
--precision; precision = -decimalPoint
--length; + (precision > length ? length : precision);
} }
if (negative) // Handle the three decimal varieties.
if (decimalPoint <= 0)
{ {
result.insertBack('-'); // Handle 0.000*000xxxx.
} bufferSlice.front = '0';
if (decimalPoint == special)
{
result.insertBack(realString);
goto ParamEnd;
}
// Should we use sceintific notation?
if ((decimalPoint <= -4) || (decimalPoint > cast(int) n))
{
if (precision > length)
{
precision = length - 1;
}
else if (precision > 0)
{
// When using scientific notation, there is one digit before the
// decimal.
--precision;
}
// Handle leading chars.
bufferSlice.front = realString[0];
bufferSlice.popFront(); bufferSlice.popFront();
if (precision != 0) if (precision != 0)
@ -592,143 +626,156 @@ package(tanya) String format(string fmt, Args...)(auto ref Args args)
bufferSlice.front = period; bufferSlice.front = period;
bufferSlice.popFront(); bufferSlice.popFront();
} }
n = -decimalPoint;
// Handle after decimal. if (n > precision)
if ((length - 1) > precision)
{ {
length = precision + 1; n = precision;
}
realString[1 .. length].copy(bufferSlice);
bufferSlice.popFrontExactly(length - 1);
// Dump the exponent.
tail[1] = 'e';
--decimalPoint;
if (decimalPoint < 0)
{
tail[2] = '-';
decimalPoint = -decimalPoint;
}
else
{
tail[2] = '+';
} }
n = decimalPoint >= 100 ? 5 : 4; fill!'0'(bufferSlice[0 .. n]);
bufferSlice.popFrontExactly(n);
tail[0] = cast(char) n; if ((length + n) > precision)
while (true)
{ {
tail[n] = '0' + decimalPoint % 10; length = precision - n;
if (n <= 3) }
{
break; realString[0 .. length].copy(bufferSlice);
} bufferSlice.popFrontExactly(length);
--n; }
decimalPoint /= 10; else if (cast(uint) decimalPoint >= length)
{
// Handle xxxx000*000.0.
n = 0;
do
{
bufferSlice.front = realString[n];
bufferSlice.popFront();
++n;
}
while (n < length);
if (n < cast(uint) decimalPoint)
{
n = decimalPoint - n;
fill!'0'(bufferSlice[0 .. n]);
bufferSlice.popFrontExactly(n);
}
if (precision != 0)
{
bufferSlice.front = period;
bufferSlice.popFront();
} }
} }
else else
{ {
if (decimalPoint > 0) // Handle xxxxx.xxxx000*000.
n = 0;
do
{ {
precision = decimalPoint < (cast(int) length) bufferSlice.front = realString[n];
? length - decimalPoint
: 0;
}
else
{
precision = -decimalPoint
+ (precision > length ? length : precision);
}
// Handle the three decimal varieties.
if (decimalPoint <= 0)
{
// Handle 0.000*000xxxx.
bufferSlice.front = '0';
bufferSlice.popFront(); bufferSlice.popFront();
++n;
if (precision != 0)
{
bufferSlice.front = period;
bufferSlice.popFront();
}
n = -decimalPoint;
if (n > precision)
{
n = precision;
}
fill!'0'(bufferSlice[0 .. n]);
bufferSlice.popFrontExactly(n);
if ((length + n) > precision)
{
length = precision - n;
}
realString[0 .. length].copy(bufferSlice);
bufferSlice.popFrontExactly(length);
} }
else if (cast(uint) decimalPoint >= length) while (n < cast(uint) decimalPoint);
if (precision > 0)
{ {
// Handle xxxx000*000.0. bufferSlice.front = period;
n = 0; bufferSlice.popFront();
do
{
bufferSlice.front = realString[n];
bufferSlice.popFront();
++n;
}
while (n < length);
if (n < cast(uint) decimalPoint)
{
n = decimalPoint - n;
fill!'0'(bufferSlice[0 .. n]);
bufferSlice.popFrontExactly(n);
}
if (precision != 0)
{
bufferSlice.front = period;
bufferSlice.popFront();
}
} }
else if ((length - decimalPoint) > precision)
{ {
// Handle xxxxx.xxxx000*000. length = precision + decimalPoint;
n = 0;
do
{
bufferSlice.front = realString[n];
bufferSlice.popFront();
++n;
}
while (n < cast(uint) decimalPoint);
if (precision > 0)
{
bufferSlice.front = period;
bufferSlice.popFront();
}
if ((length - decimalPoint) > precision)
{
length = precision + decimalPoint;
}
realString[n .. length].copy(bufferSlice);
bufferSlice.popFrontExactly(length - n);
} }
realString[n .. length].copy(bufferSlice);
bufferSlice.popFrontExactly(length - n);
} }
// Get the length that we've copied.
length = cast(uint) (buffer.length - bufferSlice.length);
result.insertBack(buffer[64 .. length]); // Number.
result.insertBack(tail[1 .. tail[0] + 1]); // Tail.
} }
else static if (isPointer!(Args[0])) // Pointer
// Get the length that we've copied.
length = cast(uint) (buffer.length - bufferSlice.length);
result.insertBack(buffer[64 .. length]); // Number.
result.insertBack(tail[1 .. tail[0] + 1]); // Tail.
}
private void formatStruct(T)(ref T arg, ref String result)
if (is(T == struct))
{
template pred(alias f)
{
static if (f == "this")
{
// Exclude context pointer from nested structs.
enum bool pred = false;
}
else
{
enum bool pred = !isSomeFunction!(__traits(getMember, arg, f));
}
}
alias fields = Filter!(pred, __traits(allMembers, T));
static if (fields.length > 0)
{
printToString!"{}"(result, __traits(getMember, arg, fields[0]));
foreach (field; fields[1 .. $])
{
result.insertBack(", ");
printToString!"{}"(result, __traits(getMember, arg, field));
}
}
}
private ref String printToString(string fmt, Args...)(return ref String result,
auto ref Args args)
{
alias Arg = Args[0];
static if (is(Unqual!Arg == typeof(null))) // null
{
result.insertBack("null");
}
else static if(is(Unqual!Arg == bool)) // Boolean
{
result.insertBack(args[0] ? "true" : "false");
}
else static if (isSomeChar!Arg || isSomeString!Arg) // String or char
{
result.insertBack(args[0]);
}
else static if (isInputRange!Arg
&& !isInfinite!Arg
&& isSomeChar!(ElementType!Arg)) // Stringish range
{
result.insertBack(args[0]);
}
else static if (is(Unqual!(typeof(args[0].stringify())) == String))
{
result.insertBack(args[0].stringify()[]);
}
else static if (is(Arg == class))
{
result.insertBack(args[0] is null ? "null" : args[0].toString());
}
else static if (is(Arg == interface))
{
result.insertBack(Arg.classinfo.name);
}
else static if (is(Arg == struct))
{
result.insertBack(Arg.stringof);
result.insertBack('(');
formatStruct(args[0], result);
result.insertBack(')');
}
else static if (isFloatingPoint!Arg) // Float
{
formatReal(args[0], result);
}
else static if (isPointer!Arg) // Pointer
{ {
char[size_t.sizeof * 2] buffer; char[size_t.sizeof * 2] buffer;
size_t position = buffer.length; size_t position = buffer.length;
@ -744,20 +791,26 @@ package(tanya) String format(string fmt, Args...)(auto ref Args args)
result.insertBack("0x"); result.insertBack("0x");
result.insertBack(buffer[position .. $]); result.insertBack(buffer[position .. $]);
} }
else static if (isIntegral!(Args[0])) // Integer else static if (isIntegral!Arg) // Integer
{ {
char[21] buffer; char[21] buffer;
result.insertBack(integral2String(args[0], buffer)); result.insertBack(integral2String(args[0], buffer));
} }
else else
{ {
static assert(false); static assert(false,
"Formatting type " ~ Arg.stringof ~ " is not supported");
} }
ParamEnd:
return result; return result;
} }
package(tanya) String format(string fmt, Args...)(auto ref Args args)
{
String formatted;
return printToString!fmt(formatted, args);
}
// One argument tests. // One argument tests.
@nogc pure @safe unittest @nogc pure @safe unittest
{ {
@ -772,7 +825,7 @@ ParamEnd:
// String printing. // String printing.
assert(format!"{}"("Some weired string") == "Some weired string"); assert(format!"{}"("Some weired string") == "Some weired string");
assert(format!"{}"(cast(string) null) == "null"); assert(format!"{}"(cast(string) null) == "");
assert(format!"{}"('c') == "c"); assert(format!"{}"('c') == "c");
// Integer. // Integer.
@ -826,6 +879,93 @@ ParamEnd:
assert(format!"{}"(cast(void*) null) == "0x0"); assert(format!"{}"(cast(void*) null) == "0x0");
} }
// Structs.
@nogc pure @safe unittest
{
static struct WithoutStringify1
{
int a;
void func()
{
}
}
assert(format!"{}"(WithoutStringify1(6)) == "WithoutStringify1(6)");
static struct WithoutStringify2
{
}
assert(format!"{}"(WithoutStringify2()) == "WithoutStringify2()");
static struct WithoutStringify3
{
int a = -2;
int b = 8;
}
assert(format!"{}"(WithoutStringify3()) == "WithoutStringify3(-2, 8)");
struct Nested
{
int i;
void func()
{
}
}
assert(format!"{}"(Nested()) == "Nested(0)");
static struct WithStringify
{
String stringify() const @nogc nothrow pure @safe
{
return String("stringify method");
}
}
assert(format!"{}"(WithStringify()) == "stringify method");
}
// Aggregate types.
@system unittest // Object.toString has no attributes.
{
import tanya.memory;
import tanya.memory.smartref;
interface I
{
}
class A : I
{
}
auto instance = defaultAllocator.unique!A();
assert(format!"{}"(instance.get()) == instance.get.toString());
assert(format!"{}"(cast(I) instance.get()) == I.classinfo.name);
assert(format!"{}"(cast(A) null) == "null");
}
// Ranges.
@nogc pure @safe unittest
{
static struct Stringish
{
string content = "Some content";
immutable(char) front() const @nogc nothrow pure @safe
{
return this.content[0];
}
void popFront() @nogc nothrow pure @safe
{
this.content = this.content[1 .. $];
}
bool empty() const @nogc nothrow pure @safe
{
return this.content.length == 0;
}
}
assert(format!"{}"(Stringish()) == "Some content");
}
private struct FormatSpec private struct FormatSpec
{ {
} }

View File

@ -393,7 +393,9 @@ struct Integer
T opCast(T)() const T opCast(T)() const
if (isIntegral!T && isSigned!T) if (isIntegral!T && isSigned!T)
{ {
return this.sign ? -(cast(Unsigned!T) this) : cast(Unsigned!T) this; return this.sign
? cast(T) -(cast(Promoted!(Unsigned!T)) (cast(Unsigned!T) this))
: cast(Unsigned!T) this;
} }
/// ///
@ -1072,7 +1074,7 @@ struct Integer
assert(h1 == 79); assert(h1 == 79);
h2 = ~h1; h2 = ~h1;
assert(h2 == ~cast(ubyte) 79); assert(h2 == cast(ubyte) ~79);
} }
/// ditto /// ditto

View File

@ -275,13 +275,37 @@ enum bool isTemplate(alias T) = __traits(isTemplate, T);
static assert(!isTemplate!(S!int)); static assert(!isTemplate!(S!int));
} }
deprecated("Use is(T == interface) instead") /**
* Tests whether $(D_PARAM T) is an interface.
*
* Params:
* T = A type.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM T) is an interface,
* $(D_KEYWORD false) otherwise.
*/
enum bool isInterface(T) = is(T == interface); enum bool isInterface(T) = is(T == interface);
deprecated("Use is(T == class) instead") /**
* Tests whether $(D_PARAM T) is a class.
*
* Params:
* T = A type.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM T) is a class,
* $(D_KEYWORD false) otherwise.
*/
enum bool isClass(T) = is(T == class); enum bool isClass(T) = is(T == class);
deprecated("Use is(T == struct) instead") /**
* Tests whether $(D_PARAM T) is a struct.
*
* Params:
* T = A type.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM T) is a struct,
* $(D_KEYWORD false) otherwise.
*/
enum bool isStruct(T) = is(T == struct); enum bool isStruct(T) = is(T == struct);
/** /**