361 Commits

Author SHA1 Message Date
db607f7602 Remove unmaintained and unfinished async code 2022-05-30 05:50:55 +02:00
cfcb1e727a Fix foldr scope 2022-05-30 05:43:08 +02:00
c7bfbe0657 Update the documentation link 2021-12-27 10:57:05 +01:00
0155039071 Replace rotate with bringToFront 2021-06-04 09:37:50 +02:00
c15a8993ec Use sockets with new IP Address structs 2021-05-29 09:50:47 +02:00
be8fcb3e1c Add Endpoint 2021-05-28 09:43:40 +02:00
d9fda61fe1 Pass tests with GDC 10.3 2021-05-27 08:14:12 +02:00
4f48544297 Allow building with GDC 10.3 2021-05-26 10:29:55 +02:00
938a1bb5b4 Replace short preconditions in the main package 2021-05-25 09:03:00 +02:00
b62cbb0647 Use std searching and iteration 2021-05-16 07:52:46 +02:00
2c21dc3429 Replace the ascii module with std.ascii 2021-05-15 13:31:47 +02:00
f0d8c616bb Replace outdated import in the documentation 2021-04-15 09:38:59 +02:00
60b0562311 Update CI information 2021-03-30 08:33:45 +02:00
92284c8541 Replace Option with Nullable 2021-03-27 10:28:49 +01:00
0fcc83d00e Remove the sys package 2021-03-12 08:48:35 +01:00
f27f62b80a Fix InputRanges for non-copyable elements 2021-03-11 10:18:02 +01:00
a227b58407 Remove deprecated functionality 2020-05-06 07:08:14 +02:00
3ce5e8153c Update copyrights 2020-05-05 07:46:10 +02:00
c68b8d1bdd Update compiler, remove deprecated modules 2020-05-04 06:03:45 +02:00
048939410c Deprecate Integer and isPseudoprime() 2019-09-03 09:13:29 +02:00
c69282a8df Work around 2.086 bugs 2019-08-28 20:50:15 +02:00
0a973b46ba Add algorithm.iteration.foldr 2019-04-24 06:53:08 +02:00
73535568b7 Merge pull request #87 from n8sh/getAndPopFrontSafety
Ensure getAndPopFront/getAndPopBack don't promote `system` to `safe`
2019-04-22 10:50:58 +02:00
b2a1a849f8 Ensure getAndPopFront/getAndPopBack don't promote system to safe 2019-04-19 09:34:30 -04:00
76bda0ac8d Add getAndPopFront()/getAndPopBack() 2019-04-17 06:27:18 +02:00
f214f3baa2 Add algorithm.iteration.foldl 2019-04-16 07:20:52 +02:00
f66935f40d Build with -dip1000. Fix #85 2019-04-15 07:05:56 +02:00
9814e5ad8e Pass allocator in all HashTable/Set constructors 2019-04-14 09:07:22 +02:00
e6c6a2d21a Make Array.get system function
.get() returns a memory block that can be changed if the original array
is manipulated after getting the slice. So the slice returned by .get()
may allow access to invalid memory.
2019-04-05 08:58:22 +02:00
d55eac3bac Refactor allSatisfy, anySatisfy, staticIndexOf 2019-04-03 18:34:39 +02:00
b55bb767e5 Merge pull request #84 from n8sh/inoutConstIteration
In tanya.algorithm.iteration.take & retro preserve const/inout for `empty`/`front`/etc.
2019-03-29 08:36:50 +01:00
2b8471fe34 Add meta.trait.hasFunctionAttributes 2019-03-24 21:59:29 +01:00
10afe47bae In tanya.algorithm.iteration.take & retro preserve const/inout for empty/front/etc. 2019-03-23 23:41:20 -04:00
76f2cd7080 Add DIP25 compatibility. Fix #83 2019-03-23 06:42:50 +01:00
ad46afb10b Move memory/package.d into memory.allocator 2019-03-22 08:18:01 +01:00
a36b51f0c3 Fix MmapPool private tests; move remaining tests 2019-03-21 14:54:16 +01:00
0fe7308a22 algorithm: import searching publically 2019-03-20 07:30:47 +01:00
20c7e47ff7 net: Add missing public imports, move tests 2019-03-19 08:47:39 +01:00
484cb13317 Separate non-documentation tests from the code 2019-03-19 07:45:52 +01:00
5ab99cf887 Move memory functions into memory.lifecycle
- move
- moveEmplace
- forward
- emplace
- swap
2019-03-17 10:56:44 +01:00
85d7a2b9ca Move memory exception into memory.lifecycle 2019-03-16 10:35:28 +01:00
b458c6a380 Make subpackages of os, sys and encoding 2019-03-05 20:47:02 +01:00
5b850d532e Move meta into a separate subpackage 2019-03-02 08:08:10 +01:00
d7dfa3f6f1 net.ip.Address6.toString() recommended notation
Fix #65.
2019-03-01 08:28:36 +01:00
8fd0452cd0 algorithm.iteration: Add singleton()
... iterating over a single value.
2019-02-25 09:27:03 +01:00
df99ea45f2 range.adapter: new arrayInserter 2019-02-24 13:14:30 +01:00
87ba58098e format.sformat: Support range-based toString() 2019-02-19 06:39:39 +01:00
5a134ce768 net.ip: Implement .toString() with output ranges 2019-02-16 08:37:45 +01:00
0835edce1d range.adapter: Add container-range adapters. Fix #67 2019-02-16 08:36:50 +01:00
a786bdbec5 Use word-wise hash instead of FNV-1a for arrays of word-aligned scalars
Also special case int-aligned scalars on 64-bit machines.
On a 64-bit machine hashing an array of pointers is now ~5.95x faster
with LDC2 and ~8.54x faster with DMD, and hashing an array of ints is
~3.34x faster with LDC2 and ~8.12x faster with DMD.
2019-02-12 10:34:18 -05:00
0bef2ef76d Add sformat() writing to an output range 2019-02-12 07:37:24 +01:00
1d3d750adb Update dmd to 2.084.1 2019-02-11 22:14:59 +01:00
0c8f1eb4ce Deprecate InputRange source for OutputRanges
An output range for E won't be automatically an output range for [E]
anymore. The same, an output range for [E] won't be automatically an
output range for E. Automatic E <-> [E] conversion seems to be a nice
feature at first glance, but it causes much ambiguity.

1) If I want that my output range accepts only UTF-8 strings but not
single characters (because it could be only part of a code point and
look like broken UTF-8 without the remaining code units), I can't do it
because an OutputRange(R, E) can't distinguish between char and string.

2) Here is an example from 2013:

import std.range;
import std.stdio;
Appender!(const(char)[][]) app;
put(app, "aasdf");
put(app, 'b');
writeln(app.data);

This outputs: ["aasdf", "\0"].
Whether it is a common case or not, such code just shouldn't compile.
2019-02-06 07:26:28 +01:00
bf197a6554 Deprecate put() as an OutputRange primitive 2019-02-04 10:49:12 +01:00
7af5c30820 move(): Give compiler an opportunity to optimize
Fix #75.
2019-02-01 06:33:41 +01:00
c1535e8752 typecons.Variant: Make public. Fix #73 2019-01-31 06:33:19 +01:00
5453f6417f typecons.Option: Deprecate alias this 2019-01-30 06:58:02 +01:00
410b865df9 typecons.Option: Fix assigning nothing 2019-01-29 08:24:58 +01:00
4566cf7857 meta.metafunction: Add Enumerate and EnumerateFrom 2019-01-28 08:30:54 +01:00
0a2798cc96 Call postblit when emplacing a struct. Fix #81 2019-01-27 07:18:53 +01:00
a505a033ab net.ip.Address: Address4/Address6 compatibility 2019-01-25 13:07:32 +01:00
1f02ba5042 net.ip: Add Address4 and Address6 wrapper 2019-01-24 07:14:15 +01:00
50aaa170fb Merge remote-tracking branch 'n8sh/retro-slicing' 2019-01-09 18:17:41 +01:00
ff7d20f167 retro supports slicing if source range supports slicing 2019-01-08 20:19:53 -05:00
03e21d4368 Remove deprecated memory.op.cmp and Entropy class 2018-12-21 20:05:23 +01:00
c293c6c809 container.array: Fix assigning non-copyable values
Fix #59.
2018-12-18 05:37:52 +01:00
e93898d837 Update dmd to 2.083.1 2018-12-17 18:04:36 +01:00
49d7452b33 Make containers work with non-copyable elements
It is the first step. The containers can be at least created with
non-copyable structs without compilation errors now.
Fix #69.
2018-11-24 06:25:55 +01:00
884dc30953 Fix emplacing POD structs 2018-11-23 13:36:31 +01:00
e67a05138e range.primitive: Support non copyable elements
... in all ranges.
2018-11-19 21:37:58 +01:00
7585bf59e7 Add test.stub. Fix #51 2018-11-18 06:32:10 +01:00
0a121d9d19 Disable length when taking from a lengthless range
Fix #79.
2018-11-13 08:29:51 +01:00
9e6f5c3105 Add algorithm.mutation.rotate 2018-11-12 07:54:52 +01:00
3f66782368 Add support for DMD 2.083.0 2018-11-11 07:27:25 +01:00
3c8f6e3435 Merge remote-tracking branch 'n8sh/take-slice' 2018-11-07 07:08:52 +01:00
ee8b7ef719 Merge remote-tracking branch 'retro-retro' 2018-11-06 16:59:57 +01:00
6b22cd60df take(take(range,...),n) is take(range, n) and use slicing in take like in takeExactly
Also take!R is the same as takeExactly!R when isInfinite!R.
2018-11-05 22:49:10 -05:00
c290c85088 retro(retro(range)) is range 2018-11-05 18:43:58 -05:00
65e2e344df Use inout in tanya.range.array functions
This is to reduce distinct generated functions in final executable.
Also add `scope` and `return` to function parameters.
2018-11-05 16:40:48 -05:00
184d307e40 Add range primitive sameHead 2018-11-04 06:31:38 +01:00
8aec781e2a memory.op.equal: Fix parameter documentation 2018-10-30 12:57:09 +01:00
1e46109e50 algorithm.mutation.destroyAll: New
Fix #71.
2018-10-29 11:14:33 +01:00
64ceb0330c Merge pull request #74 from n8sh/128-bit-fnv
Add FNV constants for size_t == ucent
2018-10-25 20:05:21 +02:00
b230685595 Add FNV constants for size_t == ucent 2018-10-25 07:07:29 -04:00
ff58b5e81c Add algorithm.mutation.initializeAll 2018-10-24 08:14:15 +02:00
373a192b3a Make hasLvalueElements work with non-copyable 2018-10-22 08:39:38 +02:00
4e8c9bd28f Use new __traits(isZeroInit) to check for a null initializer at compile time instead of runtime 2018-10-21 18:52:02 -04:00
3b5709821a Add algorithm.mutation.uninitializedFill 2018-10-20 10:42:01 +02:00
a04a04bb96 conv.emplace: Don't call a destructor
Don't call the destructor on uninitialized elements.
2018-10-14 11:30:02 +02:00
d0d682ca65 Update dmd to 2.082.1, Update GDC .gitignore 2018-10-12 19:57:49 +02:00
6d01680685 conv.emplace: Fix emplacing structs w/o this() 2018-10-08 17:51:59 +02:00
4f9927a8c3 Add algorithm.mutation.fill() 2018-10-06 16:00:08 +02:00
a8b18d7603 Deprecate Entropy (leaving platform sources alone)
Also introduces unavoidable breaking change in EntropySource interface:
poll() returns Option!ubyte instead of Nullable.
2018-10-05 13:23:57 +02:00
9364112690 net.ip: Parse embedded Ipv4. Fix #64 2018-10-03 20:49:14 +02:00
772e87739c Replace memory.op.cmp with optimized equal version
Deprecate cmp.
Fix #68.
2018-10-02 08:55:29 +02:00
2a90a812db Add algorithm.searching.count 2018-09-30 15:25:10 +02:00
e68fcc3a38 Remove code deprecated in 0.11.2 and earlier
- conv.to!String
- meta.metafunction.Tuple
- range.adapter.take
- range.adapter.takeExactly
- range.primitive: put()-ting input range into an output one
2018-09-29 09:00:43 +02:00
c5eb2f27be Add algorithm.iteration 2018-09-28 05:40:33 +02:00
349e6dfede Create separate travis job for D-Scanner 2018-09-26 06:30:05 +02:00
fd133554f3 net.ip: Implement opCmp. Fix #63 2018-09-24 06:45:44 +02:00
9ac56c50f1 typecons: Add option constructor function 2018-09-23 06:59:41 +02:00
03b45ae441 Add typecons.tuple(), Tuple construction function 2018-09-22 07:32:30 +02:00
31d4f30a49 functional.forward: Fix template visibility bug
Because of the private template forwardOne, forward couldn't be used in
other modules. forwardOne cannot be a local template either since it
accepts an alias as its template parameter.
2018-09-21 06:23:59 +02:00
180c4d3956 typecons.Option: Implement toHash forwarder 2018-09-18 22:27:54 +02:00
b0dc7b59e5 Add predicate support for algorithm.comparison.equal 2018-09-17 19:17:39 +02:00
eb796e0ddf Add bitmanip.BitFlags 2018-09-16 19:07:55 +02:00
e5569e5fea meta.trait.EnumMembers: Fix one-member enums
Produce a tuple for an enum with only one member.
2018-09-15 06:06:17 +02:00
b831a05407 Introduce hash.lookup.isHashFunction trait
Fix #66.
2018-09-14 15:16:08 +02:00
b6d1766d58 Implement compare algorithm. Fix #50 2018-09-11 10:05:15 +02:00
7f080831c5 Implement IPv6 parser, fix #49 2018-09-08 07:20:23 +02:00
94c7fd2231 Move range.adapter to algorithms + take() bugfixes
A lot of algorithms like lazy sort() can be also classified as adapters
since it wraps the original range and allows to access the elements of
the range in a particular order. The only reason why take() was in
range.adapter is that take() is trivial - it doesn't change the order of
elements but can turn an infinite range into finite one. This
distinction between trivial and non-trivial algorithms isn't absolutely
clear. So let us put all algorithms and any adapters that change the
range iteration in some way into "algorithm" package to avoid any
confusion later.

- range.adapter is renamed into algorithm.iteration
- range.adapter is deprecated
- Added missing imports for take() and takeExactly()
- takeExactly() doesn't wrap ranges that have slicing anymore
- Voldemort structs for take() takeExactly() are now static
2018-09-06 12:50:42 +02:00
5ba6d35a1b Use fixed dscanner version 2018-09-03 09:55:19 +02:00
09f434f631 net.iface: Add indexToName 2018-09-02 10:00:52 +02:00
1f615301e5 memory.op: Add findNullTerminated 2018-09-02 08:27:26 +02:00
131675d0a8 Parse for the main part of an IPv6 address 2018-09-01 11:02:10 +02:00
aa12aa9014 Add module for network interfaces 2018-09-01 10:15:23 +02:00
41878cde50 Fix #60: Copying overlapping array slices 2018-08-30 07:12:38 +02:00
0fc0aa23f7 Add constants and syscall for if_nametoindex 2018-08-28 20:39:45 +02:00
c205c087a4 Switch to COFF on x86 Windows 2018-08-26 00:10:17 +02:00
8ca88d1f01 net.ip.Address4: Reject malformed addresses 2018-08-22 06:51:20 +02:00
fa4cbb7e59 Update to 2.081.2. Remove old compilers 2018-08-17 05:44:58 +02:00
4653e94fa1 Merge remote-tracking branch 'n8sh/relax-hasher-reqs' 2018-08-12 06:17:12 +02:00
ba5833318b conv: Fix taking out of range chars for hex values 2018-08-11 14:42:09 +02:00
918d8f5450 Deprecated putting an input into an output range
Use copy instead.
2018-08-10 15:34:07 +02:00
2862cc6f50 Update asm mangling to match GDC's D frontend 2018-08-08 10:27:23 +02:00
aa4ccddf47 Add net.ip. Fix #48 2018-08-07 22:27:09 +02:00
22cffe9d6e Set: allow hasher to take arg by ref 2018-08-06 14:41:47 -04:00
abd286064b Add algorithm.mutation.copy 2018-08-05 07:19:30 +02:00
b04928d2c8 net.inet: Import range.array as well 2018-08-03 16:52:34 +02:00
ceb8e6a113 Use identity hash for integers and pointers
This is appropriate because HashArray in tanya.container.entry uses
prime numbers instead of powers of 2 for its number of buckets so there
is no pitfall if the hashes are all multiples of some power of 2.
2018-08-02 12:37:02 -04:00
900a7172bf Make format() public
Make format() public. Deprecate to!String.
2018-08-01 16:58:23 +02:00
fe0576a2d6 Fix format printing only the first argument 2018-07-29 12:51:38 +02:00
a5b84deca7 format: Add errol3 lookup 2018-07-28 13:52:59 +02:00
24056d53c5 network.socket.Linger.enabled: Add return type
network.socket.Linger.enabled: Add missing return type (void).
2018-07-25 05:40:49 +02:00
d62f29abd1 Rename meta.metafunction.Tuple into Pack
typecons.Tuples and meta.metafunction.Tuples are often used together,
from the same module. So it is reasonable give them different names.
2018-07-24 20:16:21 +02:00
f2eb99bab0 Format fixed-point numbers 2018-07-22 15:28:17 +02:00
531cae51a3 Stringish to integral type conversion. Fix #44 2018-07-18 06:33:45 +02:00
1b203507f6 conv: Fix overflow hanndling in readIntegral 2018-07-17 16:03:05 +02:00
99e06e0d04 format: Support text in the format string
Fix  #25.
2018-07-14 19:09:21 +02:00
158a47d54a Update dmd to 2.081.1. Remove 2.077.1 2018-07-13 05:39:58 +02:00
5865e355cd Fix EV_SET 0 length on 32-bit BSD 2018-07-08 12:54:47 +02:00
a94b1b0af4 Add functional module. Fix #52 2018-07-07 12:17:59 +02:00
3df4eb6259 Don't check UTF-8 correctness when inserting chars
- Fix bug when inserting char ranges that are not arrays
- Optimize insertion from the String own range
- Assume char and char ranges are correclty encoded (as it actually
should be) and don't throw an exception. This should make the most
common use cases nothrow (Fix #19). Dchars and Wchars are still encoded
because they should be converted to UTF-8 before inserting anyway.
2018-07-06 05:36:13 +02:00
a332d727af Implement errol2 for floating point formatting
Handles whole floating point numbers in the range between 2^54 and 2^131.
2018-07-02 10:47:05 +02:00
8241943a58 Add uint128 tailored for errol2 computations 2018-07-02 10:26:45 +02:00
d54e06f43c Iterate hash table by key or by value 2018-06-29 20:43:05 +02:00
5e901f505c Make HashTable work complex types as key
- Add toHash() function for String
- The key type shouldn't match exact for a lookup.
The key type and lookup key type should be comparable.
- Move elements when inserting if passed by value.
2018-06-28 12:14:45 +02:00
533fa3b023 container.HashTable: Fix infinite rehashing when inserting
Fix #53.
2018-06-27 05:45:53 +02:00
adf2d8b689 Add Option to typecons
Fix #47.
2018-06-26 04:25:32 +02:00
74ece7ddf4 Replace floating pointer formatter with errol1 2018-06-23 16:21:19 +02:00
411e45ec5c Remove functions deprecated in 0.9.0 2018-06-22 07:11:56 +02:00
f51e9405c9 Update socket documentation 2018-06-20 07:59:37 +02:00
de15281ccb Tuple with more than two fields
Fix #41.
2018-06-19 05:44:15 +02:00
a86b6690f0 Implement auto-decoding free equal comparison
Fix #39.
2018-06-12 20:19:06 +02:00
15f7994187 Add takeExactly
Fix #43.
2018-06-10 19:03:26 +02:00
37b0afe290 take: Remove moveFront, moveBack, moveAt 2018-06-10 14:46:40 +02:00
cd9960db2a Add take range adapter 2018-06-10 14:46:40 +02:00
7357503c5a Update 2.080 series to 2.080.1 2018-06-09 05:05:30 +02:00
173ae115ee readIntegral: Support base between 2 and 36 2018-06-08 21:05:35 +02:00
7561b964d3 Make intToString -> readString more generic
Make readString work with any char range and unsigned integral type.
2018-06-07 07:23:39 +02:00
c663703221 container.list: Remove deprecated list length property 2018-06-01 14:13:27 +02:00
58af2fd89b encoding.ascii: Make static const data immutable 2018-05-31 18:43:35 +02:00
52ec88bd04 async: Annotate system tests 2018-05-31 18:43:21 +02:00
bfe0748a63 Insert a range into the hash table and set 2018-05-30 18:50:52 +02:00
61814d5383 Make an independent function for converting port string 2018-05-23 05:10:44 +02:00
c268696ee9 HashTable/Set: Add proper assignment 2018-05-20 21:58:15 +02:00
9efbc9d5e0 Make Array postblit safe if possible 2018-05-18 07:43:18 +02:00
c511b97b1b container.Set and HashTable: Fix constructors 2018-05-17 05:31:14 +02:00
385ec19e2f hash.lookup: Reformat the docs 2018-05-17 05:30:49 +02:00
205d7a080e Add KeyValue alias for value tuple 2018-05-14 21:55:49 +02:00
d545d6900e Make HashTable Range return Pair 2018-05-14 19:23:22 +02:00
3ed46117d1 Port Set ranges for HashTable 2018-05-14 19:23:22 +02:00
00dbb224f7 Move length tracking to HashArray 2018-05-14 19:23:22 +02:00
9cf1b6f491 Use HashArray as internal storage 2018-05-14 19:23:22 +02:00
bdce5cda6a Add HashTable container 2018-05-14 19:23:22 +02:00
faf952b30e Rename Pair to Tuple 2018-05-12 06:11:24 +02:00
53620cdddf Improve preconditions for the container.Set 2018-05-11 05:43:14 +02:00
41a8e32351 Switch to travis-ci.com 2018-05-10 06:13:38 +02:00
2ec750ca05 Fix math.nbtheory linkage to asm
Don't use extern for templated functions. If the function argument is
const, it gets a different mangling. So define a private function for
each floatint point length and call it from template.
2018-05-08 18:07:42 +02:00
6ed2992862 Remove unused variables 2018-05-06 07:03:11 +02:00
5c8c0ce4d8 Add dmd 2.080.0 support 2018-05-05 05:22:04 +02:00
cd1a38f402 Move Smallest and Largest to meta.transform
Smallest and Largest choose the smallest or largest (according to
.sizeof property) type in the list of types. These templates get a list
of types and produce a type, so they are transformations.
2018-05-02 15:50:28 +02:00
4f6ce116bc Add documented tests for Set.empty and Set.clear() 2018-05-01 15:56:07 +02:00
c4424e7e01 Track hash Set length
Can be used later to rehash the hash table if it is full up to some
percentage.
2018-04-30 12:51:35 +02:00
18d54b4b18 HashArray as an internal store for hash containers 2018-04-29 09:12:48 +02:00
36646aa2c4 container.Set: Rewrite arch dependent tests 2018-04-28 18:07:41 +02:00
702d1b02e0 Make allocator getter public 2018-04-28 17:57:07 +02:00
8733b93ca0 container.Set: Support customizable hasher 2018-04-28 17:49:49 +02:00
55c36d22a0 Make isType public 2018-04-27 11:32:41 +02:00
6e2852000b Deprecate math.min/max in favour of tanya.algorithm 2018-04-27 11:32:22 +02:00
c0f9e5be10 Replace std min/max. Fix #35 2018-04-26 10:23:06 +02:00
3468d6ea00 Accept/return as inout in min/max 2018-04-26 08:06:06 +02:00
ed5fa91e64 Merge remote-tracking branch 'origin/master' into feature/min_max 2018-04-25 15:13:03 +02:00
2185a70ac8 Fix #33 2018-04-25 13:09:34 +02:00
b94da1f58a Replace SocketError with ErrorCode.ErrorNo 2018-04-25 12:59:38 +02:00
3f9b500e20 Add CommonType 2018-04-24 15:45:47 +02:00
86053de8c9 Add min/max algorithms 2018-04-22 12:08:33 +02:00
e8222123e6 Use syscall instead of mmap and munmap 2018-04-22 08:07:20 +02:00
5cac28c093 Add new comparison traits
- allSameType
- isEqualityComparable
- isOrderingComparable
2018-04-21 06:38:32 +02:00
5e40424f7d net.inet: Replace CTFE-pow with pow operator 2018-04-20 15:15:00 +02:00
964a7af32f Fix list assertions for release build 2018-04-18 14:23:12 +02:00
40c961867e Remove deprecated traits and queue 2018-04-18 06:34:28 +02:00
3fee712c6c Implement DList.popFirstOf and DList.popLastOf
Fix #37.
2018-04-17 14:46:12 +02:00
012c2d4c18 Remove support for dmd 2.076.1 2018-04-15 06:50:37 +02:00
d267a9cc64 Implement SList.popFirstOf
Fix #36.

Slicing for the SList on top of the existing SRange would be inefficent.
There would be two cases:
- Range iterates till the end of the list.
- Range iterates till some element "end".

If both cases are implemented in the same range, this range should check
for both conditions (end of the list and "begin == end") instead of only
one (end of the list).

Introducing a different range is undesirable since all containers have
currently only one range.
2018-04-14 16:15:35 +02:00
ddb02e41eb Add dscanner style check to CI
Fix #38.
2018-04-12 17:14:22 +02:00
d157e88b7a Fix import order in math.random 2018-04-08 05:59:14 +02:00
d5064fa2b2 Add missing tail isn't null assertion 2018-04-07 19:20:08 +02:00
f15a90543f Remove support for moveFront/moveBack/moveAt
Range elements are movable (mobile) if they are returned by reference
and can be moved or if the elements doesn't define an elaborate postblit
constructor. Allowing to define custom moveFront/moveBack/moveAt makes
the range definition more complex (particulary writing range adapters)
without a good reason.
2018-04-03 21:44:50 +02:00
a0ac8355f9 Fix #29 2018-04-01 10:34:18 +02:00
9b1f72472f Deprecate SList.length and DList.length
As they have O(n) complexity. The lists length is unknown without
iterating.
2018-03-31 08:21:15 +02:00
af45de842e Take MmapPool from the standard builds 2018-03-29 16:54:56 +02:00
792d289541 range.primitive: Add missing rparen to the docs 2018-03-27 05:19:14 +02:00
92f21a95cf Add hashing pointers 2018-03-27 05:18:46 +02:00
72140a8583 Add documentation for the hash function 2018-03-27 05:09:44 +02:00
442fa5b46a Fix hashing scalar types 2018-03-27 05:09:22 +02:00
0d6d8f6a91 Add hash combining for ranges 2018-03-27 05:09:09 +02:00
cefc4e24b5 Add FNV-1a test vectors 2018-03-27 05:08:55 +02:00
1adc4cd868 Add hash.lookup module 2018-03-27 05:08:28 +02:00
8faccbada4 Deprecate meta.trait.hasMember 2018-03-26 20:38:57 +02:00
9fb043ba65 Fix typeof(null) being a pointer for isPointer 2018-03-25 09:19:35 +02:00
162db622ea Add assignable-, lvalue- and swappable checks
... for ranges.

Also adds "put" for the output ranges.

Fix #34.
2018-03-23 08:49:24 +01:00
a7c1e642e9 Implement moveFront, moveBack, moveAt
... and hasMobileElements.
2018-03-22 10:44:58 +01:00
7829b1fe06 Remove static std.range import 2018-03-22 10:44:52 +01:00
cb742eec82 meta.trait: Deprecate one-liner
These one-liners are useful for meta-programming but they can be easely
implemented. It isn't possible to implement all possible variants in a
generic library, so it is better they are defined in the user code.

Deprecated traits:
- isPOD
- sizeOf
- alignOf
- isSame
- isTemplate
- isInterface
- isClass
- isStruct
- isEnum
2018-03-21 10:10:55 +01:00
341068488d meta.trait: Make unittest structs static 2018-03-21 10:04:05 +01:00
9b0bc77b7a async: Remove unused imports 2018-03-21 08:30:47 +01:00
c9e4871fb5 algorithm.mutation: Fix param name in the docs 2018-03-21 08:15:58 +01:00
1f4ab88254 typecons.Pair: Add better documentation unittests 2018-03-21 08:14:52 +01:00
7af5b4db72 metafunction: Make Set and Tuple to structs
It allows to use alias this to access the elements by index.
2018-03-20 17:20:13 +01:00
363ebbe3df Extend release policy 2018-03-18 05:53:38 +01:00
ecd74cbf1e Describe NogcD subset 2018-03-17 08:17:51 +01:00
80a177179d Add hash table to the "Current status" branches 2018-03-15 05:46:42 +01:00
2532d49105 Ignore .lib files 2018-03-11 11:40:46 +01:00
abfccc35a2 Merge remote-tracking branch 'origin/feature/queue-dlist'
Fix #31.
2018-03-10 07:41:02 +01:00
629071f934 Add information that DList can be used as a queue 2018-03-10 07:17:43 +01:00
17cb592b13 Replace Queue with DList 2018-03-09 08:19:17 +01:00
82f41844b1 container.list: Document front/back preconditions 2018-03-09 08:00:28 +01:00
8fa033a49f Fix #32 2018-03-09 05:27:32 +01:00
e77a499fa2 Annotate typecons unittests 2018-03-07 06:52:35 +01:00
54bcec216e Deprecate MmapPool for the standard build
Mallocator is the default allocator now and should be used instead.
2018-03-06 05:29:15 +01:00
fbbdb36853 Use defaultAllocator in the async
Instead of hard-coded MmapPool.
2018-03-05 17:42:44 +01:00
b795267e75 Rename ErrorCode.text() to toString() 2018-03-04 10:43:24 +01:00
81cbb96d45 Merge remote-tracking branch 'n8sh/isRandomAccessRange-definition' 2018-03-04 09:29:09 +01:00
467335460e Decouple isRandomAccessRange from isForwardRange and isBidirectionalRange 2018-03-04 03:02:18 -05:00
dc3b083097 Add dmd 2.079.0 2018-03-03 08:34:06 +01:00
16c5fa12df Implement Error.text()
Error.text() returns an error description.
2018-03-02 06:48:03 +01:00
9bf8754711 Fix setting new head/tail after removing in DList 2018-02-26 08:09:14 +01:00
760cea163d Add a workaround for dmd 2.076.1 on OSX 2018-02-25 21:33:29 +01:00
03c40ecace Fix removing all elements from DList 2018-02-25 18:25:19 +01:00
9c70e9a058 Annotate list unittests 2018-02-25 15:42:32 +01:00
5ae20512af Fix inserting before/after a range into the string 2018-02-25 12:53:42 +01:00
d30de300d6 Fix slicing a null pointer when deallocating 2018-02-25 11:09:57 +01:00
464a0fecbb Make math.nbtheory.ln to a template function 2018-02-25 05:38:21 +01:00
84d6e207c5 Ignore dub.selections.json 2018-02-24 06:42:06 +01:00
af942116e4 Merge remote-tracking branch 'n8sh/getrandom-syscall'
Fix #18.
2018-02-22 05:25:59 +01:00
7ee4af9e79 Use correct getrandom linux syscall on non-x86_64 2018-02-21 04:49:48 -05:00
9876d9245c Implement PlatformEntropySource for macOS, Microsoft Windows, NetBSD, OpenBSD, Solaris 2018-02-21 03:18:52 -05:00
bd2b88f16e Update latest supported dmd to 2.078.3 2018-02-16 16:35:53 +01:00
2946fd7f81 Update dmd to 2.078.2 2018-02-15 18:33:54 +01:00
2cda82eeea Fix handling of misaligned bytes in fill 2018-02-04 07:23:56 +01:00
e9f70853c6 Fix #12 2018-02-02 16:13:55 +01:00
4aaa71a7d0 Format ranges 2018-02-02 14:34:36 +01:00
cbc68c2c43 Implement formatting for enums 2018-02-01 16:29:13 +01:00
048ddf21ff Replace body with do 2018-01-31 12:05:06 +01:00
fd02c411e1 Update latest dmd version to 2.078.1 2018-01-23 05:21:19 +01:00
b69d737845 Add typeid formatting tests 2018-01-16 17:44:09 +01:00
904451ccaa Remove moved and deprecated conv module 2018-01-14 19:13:12 +01:00
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
7e7bf40f70 Move remaining to methods to tanya.conv 2017-11-29 19:09:58 +01:00
642717883e Add boolean and null formatting 2017-11-29 18:44:51 +01:00
85be35c5e0 Make floating formatting safe 2017-11-29 07:49:20 +01:00
664298f038 Remove buffer argument from format 2017-11-28 22:11:19 +01:00
c199cdd47c Merge changes to reals formatting from master 2017-11-28 09:32:20 +01:00
3a24e9e462 Make pointer to string conversion safer 2017-11-27 15:10:17 +01:00
f334e6a1a0 Check format specifier at compile time 2017-11-25 22:29:45 +01:00
72d5760589 Change default pointer format 2017-11-25 19:01:20 +01:00
b28dde9d8e Remove triplet comma 2017-11-25 17:10:59 +01:00
b612e978bf format: Add format string tests 2017-11-25 15:24:45 +01:00
02d1d8218b Port vsprintf 2017-11-25 15:11:43 +01:00
fbf6ec5250 format: Check if the scientific form is to be used 2017-11-25 14:59:27 +01:00
ac317aa9d6 math.min: Drop useless second isFloatingPoint check 2017-11-19 22:37:15 +01:00
10022d158c Replace aho/ali usage with HP 2017-11-16 19:28:44 +01:00
a38242d0ac Make real2String more readable 2017-11-16 19:19:18 +01:00
a84c71f26d Revert usage of "do" instead of "body"
And fix GCC build.
2017-11-16 19:15:56 +01:00
7797f0a1fe format.conv.number2String -> format.integral2String (intern) 2017-11-12 11:57:47 +01:00
4bbc8b510a conv: Use assertThrown to check ConvException is thrown 2017-11-12 11:44:45 +01:00
87ea1f98dc Add range primitives that remove several elements
- isOutputRange
- popFrontN
- popFrontExactly
- popBackN
- popBackExactly
2017-11-05 07:00:10 +01:00
9422888b6c Support dmd 2.075.1 - 2.077.0 2017-11-04 00:35:47 +01:00
13407fcf8a math: Add min/max 2017-11-02 06:00:11 +01:00
e06cc5a071 Fix moveEmplace for static arrays 2017-11-01 14:27:39 +01:00
12fb9ff9f6 Add algorithm.mutation.swap 2017-11-01 13:03:48 +01:00
392cdcf192 Fix moveEmplace not being pure 2017-11-01 12:30:27 +01:00
09b6655b9a memory.op: Check for valid .ptr and .length
typeid(T).initializer can return an array, whose .ptr is null but the
length not. Assert that .ptr and .length are consistent.
2017-11-01 00:01:43 +01:00
7a2768340e Add algorithm package with move and moveEmplace 2017-10-29 07:51:00 +01:00
414d7a11a8 Add meta.trait.Fields 2017-10-27 20:28:34 +02:00
0d69c7fc79 Make math.mp.Integer pure 2017-10-24 11:50:32 +02:00
b023146cb3 Update contributing guidelines 2017-10-21 14:36:34 +02:00
d1d55be7c2 Fix lowerHexDigits string 2017-10-18 06:40:22 +02:00
7b21238db7 String: Fix byCodePoint.popFront for multibyte chars 2017-10-14 13:47:16 +02:00
e316631f6e Add test package 2017-10-12 07:41:35 +02:00
fdf902c755 Update dmd 2.076 to 2.076.1 2017-10-10 07:03:04 +02:00
5d6f8e5299 Implement pure onOutOfMemory 2017-10-10 06:59:34 +02:00
87bfd77373 container.string: Add missing postblit 2017-10-08 15:53:29 +02:00
17005e4ac9 Fix isInnerClass for templates, sort unittest attributes 2017-10-06 12:28:14 +02:00
85ad88bc4d Rename isPolymorphic into isPolymorphicType 2017-10-06 12:06:47 +02:00
211f590caa Tests and better documentation for memory.stateSize 2017-10-06 07:45:46 +02:00
2f4dd34582 Replace isInterface, isClass, isStruct with isPolymorphic 2017-10-05 07:12:27 +02:00
7e93bcdeeb meta: Add canFind and isInnerClass 2017-10-04 06:06:26 +02:00
e4cd57a615 math.nbtheory: Implement natural logarithm 2017-10-02 14:55:30 +02:00
74b085b88d Sort imports 2017-10-01 19:03:42 +02:00
a576c36d02 Replace memcpy/memmove with copy/copyBackward 2017-09-30 08:15:02 +02:00
1056a2984e Fix #303
Allocation schema is displayed incorrectly in HTML.
Add pre-tag for the schema.
2017-09-27 17:56:15 +02:00
faebf3e4d5 Fix #304
Replace inline assembly with GAS.
2017-09-26 08:26:12 +02:00
20e7df386b Ignore dub_platform_probe- files 2017-09-25 07:51:03 +02:00
15d9cda755 Add info about supporting GDC 2017-09-24 18:08:47 +02:00
ee48c25328 Replace "Ditto." with "ditto"
ddox doesn't recognize "Ditto.".
2017-09-22 04:08:50 +02:00
4612d5eb6d Add tanya.encoding.ascii 2017-09-21 06:57:49 +02:00
8d3a4860e6 Add memory.op.find for looking for a byte in a memory block 2017-09-20 08:31:54 +02:00
3df6c83376 Move formatting development to the io branch 2017-09-19 15:10:24 +02:00
7445d42ad4 Add thrd_current for x86-64 linux 2017-09-19 06:16:43 +02:00
14f91b6942 Don't import math submodules publically 2017-09-18 12:28:13 +02:00
be551e9349 Add docs and tests for fp classificators 2017-09-18 11:31:37 +02:00
586d12b6c7 Classificators for double extended floating point numbers 2017-09-17 10:30:12 +02:00
27146f7e0c Add tanya.math.fp 2017-09-16 22:35:31 +02:00
9b54017840 Move all windows specific definitions from network.socket to the sys-package 2017-09-15 10:58:23 +02:00
aabb6334be Import extern windows fill/copy memory functions 2017-09-14 18:49:13 +02:00
ce425b9ce5 Move simple socket definitions to sys.windows 2017-09-14 07:31:26 +02:00
3e9ca359da math: Add floating point support to abs 2017-09-13 06:43:49 +02:00
3705cf387e Add syscalls to x86-64 linux 2017-09-12 06:23:28 +02:00
edc3296083 Drop support for dmd 2.073.2, remove deprecations 2017-09-12 06:07:16 +02:00
e8143bd0cc Fix template constraints style in tanya.math 2017-09-11 06:48:47 +02:00
3eb8618c32 Add range.primitive 2017-09-10 10:35:05 +02:00
3567a6608e Add generic description for 'meta' package 2017-09-09 11:48:30 +02:00
520bd399a3 Add template-time Set and set-theoretic metafunctions 2017-09-05 05:51:34 +02:00
d38e33593e Add traits for working with UDAs 2017-09-03 00:00:43 +02:00
34b79ad46e Update compiler version list in the README 2017-09-02 09:48:28 +02:00
515bf619e8 Add support for dmd 2.076.0 2017-09-01 19:38:44 +02:00
617eaab9a2 tanya.format: Cast lookup array index to size_t 2017-08-30 12:20:42 +02:00
d946b598fd Add internal sprintf-compatible format function
format() has full support for sprintf format but is written completely in D.
It is currently internal, since it is not typesafe and uses GC at one place.
After some work the function can be made public.
2017-08-29 10:38:03 +02:00
e9d7e9eb73 Add documention for newly added metafunctions
Docs for:
* ZipWith
* Min
* Max

Unittests for "Instantiate".
2017-08-28 16:07:02 +02:00
4dbfbe9874 Add new metafunctions: Min, Max, ZipWith
Documentation follow
2017-08-27 15:32:05 +02:00
25d59ffdda Remove "static" prefix from metafunctions 2017-08-26 10:37:22 +02:00
2c064eb05b Add hasElaborate traits 2017-08-25 14:50:15 +02:00
c9a4a2f651 Add "native" configuration and TanyaPhobos version 2017-08-25 00:29:43 +02:00
0e99effaeb net.inet: Remove htonl/htons based unit tests 2017-08-24 07:45:16 +02:00
0f1e53b4b9 format.conv: Replace loop with copy() 2017-08-22 12:47:13 +02:00
666d59c231 Add traits for checking if class, iface, struct
They are useful for compile-time algorithms like Filter, StaticMap and
so on.
2017-08-22 11:12:41 +02:00
ce90b4865b Make front and popFront for arrays public
This commit adds tanya.range.array module which contains functions to make
the arrays act as ranges. These functions don't do any auto-decoding for
strings.
2017-08-21 06:49:02 +02:00
beb5d6963b Complete tanya.meta.metafunction 2017-08-20 12:29:48 +02:00
107 changed files with 22350 additions and 12712 deletions

9
.gitignore vendored
View File

@ -1,14 +1,21 @@
# Binary
*.[oa]
*.exe
*.lib
# D
.dub
dub.selections.json
__test__*__
__test__*__.core
/tanya-test-library*
tanya-*test-*
/dub_platform_probe[_-]*
/docs/
/docs.json
/*.lst
# Ninja build
.ninja_*

View File

@ -1,33 +0,0 @@
sudo: false
os:
- linux
- osx
language: d
d:
- dmd-2.075.1
- dmd-2.074.1
- dmd-2.073.2
env:
matrix:
- ARCH=x86_64
- ARCH=x86
addons:
apt:
packages:
- gcc-multilib
before_script:
- if [ "$PS1" = '(dmd-2.075.1)' ]; then
export UNITTEST="unittest-cov";
fi
script:
- dub test -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC
after_success:
- test "$UNITTEST" = "unittest-cov" && bash <(curl -s https://codecov.io/bash)

View File

@ -1,5 +0,0 @@
# Contributor Code of Conduct
This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters.
For more information please visit the [No Code of Conduct](https://github.com/domgetter/NCoC) homepage.

View File

@ -1,92 +0,0 @@
# Contributing
Tanya is a project in active development, therefore any help is appreciated. Thank you for considering contributing
to it, feel welcome.
These guidelines describe ways to get started.
## Ways to get involved
* **Reporting a problem**: [Report](https://issues.caraus.io/projects/tanya/issues) bugs and usage problems you
encounter.
* **Fixing issues**: [The bug tracker](https://issues.caraus.io/projects/tanya/issues) contains a list of issues you
can work on.
* **Documentation**: You can improve API documentation by correcting grammar errors, completing existing texts and
writing new ones, or providing usage examples.
* **Testing**: Test coverage is important for a library. Writing tests is not only helpful, but is also a great way
to get a feel for how tanya works.
* **Adding new features**: Tanya is a growing library. If you think some feature is missing, you can suggest
and implement this.
## Opening an issue
If you have found a bug, an error, have some question, or suggestion, open in issue. I'll try to answer as soon
as I can. Tanya uses an external
[bug tracker](https://issues.caraus.io/projects/tanya/issues). You should
[register](https://issues.caraus.io/account/register) before you can report your issue. There is also a list
of open issues that mirror the current development process and progress. If you're looking for a challenge, just
pick an issue you are interested in and start working on it. Fill free to comment on the issue to get more
information.
Some issues have a category assigned to them. Such issues belong mostly to a larger part of the library that is
currently in development. The category specifies then the git branch development happens on. The remaining issues
can be fixed directly in master.
In the [roadmap](https://issues.caraus.io/projects/tanya/roadmap) you can find a list of issues that are planned
to be fixed till a specific release. Version numbers refer to the versions in the
[git repository](https://github.com/caraus-ecms/tanya/releases).
## Contribution process
### Creating a pull request
I accept GitHub pull requests. Creating a pull request is like sending a patch with the suggested change.
First you have to [fork](https://guides.github.com/activities/forking/) the repository. Clone your fork locally
with `git clone` and create a new branch where you want to work, for example:
```shell
git checkout -b bugfix-x
```
Commit your changes to your fork:
```shell
git commit -m "Fix X"
git push -u origin bugfix-x
```
After that if you visit your fork on GitHub, GitHub will suggest to create pull request. Just follow the steps
described on GitHub to finish the process. See
[Using Pull Requests](https://help.github.com/articles/about-pull-requests/) for more information.
Please ensure that your fork is even with the upstream (original) repository. If not, you have to rebase your branch
on upstream/master before submitting a pull request. See https://help.github.com/articles/syncing-a-fork/ for a
step-by-step guide.
### Fixing a bug
Add an unittest that demonstrates the bug along with a short description:
```d
// Issue ###: https://issues.caraus.io/issues/###.
private unittest
{
}
```
### Adding new features
* Use Ddoc to document the feature.
* Add some unittests that prevent new bugs and demonstrate how the feature is supposed to work.
### Style guide
Make sure your changes follow [The D Style](https://dlang.org/dstyle.html) (including
[Additional Requirements for Phobos](https://dlang.org/dstyle.html#phobos).
## Questions and suggestions
* [Open an issue](https://issues.caraus.io/projects/tanya/issues)
* [Send an email](mailto:info@caraus.de)

View File

@ -1,45 +1,50 @@
# Tanya
[![Build status](https://travis-ci.org/caraus-ecms/tanya.svg?branch=master)](https://travis-ci.org/caraus-ecms/tanya)
[![Build status](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/master?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/master)
[![codecov](https://codecov.io/gh/caraus-ecms/tanya/branch/master/graph/badge.svg)](https://codecov.io/gh/caraus-ecms/tanya)
[![CI/CD](https://img.shields.io/badge/CI-CD-brightgreen)](https://build.caraus.tech/go/pipelines)
[![Dub version](https://img.shields.io/dub/v/tanya.svg)](https://code.dlang.org/packages/tanya)
[![Dub downloads](https://img.shields.io/dub/dt/tanya.svg)](https://code.dlang.org/packages/tanya)
[![License](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
[![License: MPL 2.0](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](https://opensource.org/licenses/MPL-2.0)
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
guarantee with @nogc attribute that there are no hidden allocations on the
Garbage Collector heap. Everything in the library is usable in @nogc code.
Tanya extends Phobos functionality and provides alternative implementations for
data structures and utilities that depend on the Garbage Collector in Phobos.
* [Bug tracker](https://issues.caraus.io/projects/tanya/issues)
* [Documentation](https://docs.caraus.io/tanya)
* [Contribution guidelines](CONTRIBUTING.md)
Tanya provides data structures and utilities to facilitate painless systems
programming in D.
- [API Documentation](https://docs.caraus.tech/tanya)
## Overview
Tanya consists of the following packages and (top-level) modules:
* `async`: Event loop (epoll, kqueue and IOCP).
* `algorithm`: Collection of generic algorithms.
* `bitmanip`: Bit manipulation.
* `container`: Queue, Array, Singly and doubly linked lists, Buffers, UTF-8
string, Hash set.
string, Set, Hash table.
* `conv`: This module provides functions for converting between different
types.
* `format`: Formatting and conversion functions.
* `hash`: Hash algorithms.
* `math`: Arbitrary precision integer and a set of functions.
* `memory`: Tools for manual memory management (allocators, smart pointers).
* `meta`: Template metaprogramming. This package contains utilities to acquire
type information at compile-time, to transform from one type to another. It has
also different algorithms for iterating, searching and modifying template
arguments.
* `net`: URL-Parsing, network programming.
* `network`: Socket implementation. `network` is currently under rework.
After finishing the new socket implementation will land in the `net` package and
`network` will be deprecated.
* `os`: Platform-independent interfaces to operating system functionality.
* `range`: Generic functions and templates for D ranges.
* `test`: Test suite for unittest-blocks.
* `typecons`: Templates that allow to build new types based on the available
ones.
## Basic usage
## NogcD
To achieve programming without the Garbage Collection tanya uses a subset of D:
NogcD.
### Allocators
@ -103,7 +108,7 @@ catch (Exception e)
}
```
### Containers
### Built-in array operations and containers
Arrays are commonly used in programming. D's built-in arrays often rely on the
GC. It is inconvenient to change their size, reserve memory for future use and
@ -133,43 +138,50 @@ int i = arr[7]; // Access 8th element.
There are more containers in the `tanya.container` package.
### Immutability
Immutability doesn't play nice with manual memory management since the
allocated storage should be initialized (mutated) and then released (mutated).
`immutable` is used only for non-local immutable declarations (that are
evaluated at compile time), static immutable data, strings (`immutable(char)[]`,
`immutable(wchar)[]` and `immutable(dchar)[]`).
### Unsupported features
The following features depend on GC and aren't supported:
- `lazy` parameters (allocate a closure which is evaluated when then the
parameter is used)
- `synchronized` blocks
## Development
### Supported compilers
| dmd |
|:-------:|
| 2.075.1 |
| 2.074.1 |
| 2.073.2 |
### Current status
Following modules are under development:
| Feature | Branch | Build status |
|----------|:---------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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) |
| 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
3-week release cycle.
Deprecated features are removed after one release (in approximately 6 weeks after deprecating).
| DMD | GCC |
|:-------:|:---------:|
| 2.098.1 | 11.2 |
## Further characteristics
* Tanya is a native D library without any external dependencies.
- Tanya is a native D library
* Tanya is cross-platform. The development happens on a 64-bit Linux, but it
is being tested on Windows and FreeBSD as well.
- Tanya is cross-platform. The development happens on a 64-bit Linux, but it
is being tested on Windows and FreeBSD as well
* The library isn't thread-safe yet.
- Tanya favours generic algorithms therefore there is no auto-decoding. Char
arrays are handled as any other array type
- The library isn't thread-safe yet
- Complex numbers (`cfloat`, `cdouble`, `creal`, `ifloat`, `idouble`, `ireal`)
aren't supported
## Feedback
Any feedback about your experience with tanya would be greatly appreciated. Feel free to
[contact me](mailto:info@caraus.de).
[contact me](mailto:belka@caraus.de).

View File

@ -1,60 +0,0 @@
platform: x64
os: Visual Studio 2015
environment:
matrix:
- DC: dmd
DVersion: 2.075.1
arch: x64
- DC: dmd
DVersion: 2.075.1
arch: x86
- DC: dmd
DVersion: 2.074.1
arch: x64
- DC: dmd
DVersion: 2.074.1
arch: x86
- DC: dmd
DVersion: 2.073.2
arch: x64
- DC: dmd
DVersion: 2.073.2
arch: x86
skip_tags: true
install:
- ps: function SetUpDCompiler
{
$env:toolchain = "msvc";
$version = $env:DVersion;
Invoke-WebRequest "http://downloads.dlang.org/releases/2.x/$($version)/dmd.$($version).windows.7z" -OutFile "c:\dmd.7z";
echo "finished.";
pushd c:\\;
7z x dmd.7z > $null;
popd;
}
- ps: SetUpDCompiler
before_build:
- ps: if($env:arch -eq "x86"){
$env:compilersetupargs = "x86";
$env:Darch = "x86";
}
elseif($env:arch -eq "x64"){
$env:compilersetupargs = "amd64";
$env:Darch = "x86_64";
}
- ps: $env:PATH += ";C:\dmd2\windows\bin;";
- call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall" %compilersetupargs%
build_script:
- echo dummy build script - dont remove me
test_script:
- echo %Darch%
- echo %PATH%
- 'dub --version'
- '%DC% --version'
- dub test -b unittest --arch=%Darch% --compiler=%DC%

View File

@ -1,3 +0,0 @@
ignore:
- "source/tanya/async/event/iocp.d"
- "source/tanya/async/iocp.d"

View File

@ -23,7 +23,7 @@ if_else_same_check="skip-unittest"
; Checks for some problems with constructors
constructor_check="skip-unittest"
; Checks for unused variables and function parameters
unused_variable_check="disabled"
unused_variable_check="skip-unittest"
; Checks for unused labels
unused_label_check="skip-unittest"
; Checks for duplicate attributes
@ -74,7 +74,7 @@ lambda_return_check="skip-unittest"
; Check for auto function without return statement
auto_function_check="skip-unittest"
; Check for sortedness of imports
imports_sortedness="disabled"
imports_sortedness="skip-unittest"
; Check for explicitly annotated unittests
explicitly_annotated_unittests="disabled"
; Check for useless usage of the final attribute

View File

@ -1,17 +1,79 @@
{
"name": "tanya",
"description": "General purpose, @nogc library. Containers, networking, memory management, utilities",
"description": "@nogc library. Containers, networking, metaprogramming, memory management, utilities",
"license": "MPL-2.0",
"copyright": "(c) Eugene Wissner <info@caraus.de>",
"copyright": "© Eugene Wissner <info@caraus.de>",
"authors": [
"Eugene Wissner"
],
"targetType": "library",
"dependencies": {
"tanya:meta": "*",
"tanya:os": "*",
"tanya:middle": "*",
"tanya:test": "*"
},
"dependencies-linux": {
"mir-linux-kernel": "~>1.0.0"
},
"subPackages": [
"./meta",
"./os",
"./middle",
"./test"
],
"configurations": [
{
"name": "library"
}
"name": "library",
"targetType": "staticLibrary",
"versions": ["TanyaPhobos"]
},
{
"name": "dynamic",
"targetType": "dynamicLibrary",
"versions": ["TanyaPhobos"]
},
{
"name": "native",
"targetType": "library",
"platforms": ["linux-x86_64"],
"versions": ["TanyaNative"]
},
{
"name": "unittest",
"versions": ["TanyaPhobos"],
"importPaths": [
"./source",
"./tests"
],
"sourcePaths": [
"./source",
"./tests"
]
},
{
"name": "unittest-native",
"platforms": ["linux-x86_64"],
"versions": ["TanyaNative"],
"importPaths": [
"./source",
"./tests"
],
"sourcePaths": [
"./source",
"./tests"
]
}
],
"dflags-dmd": ["-dip1000"],
"libs-windows": ["advapi32"],
"libs-windows-x86_mscoff": ["iphlpapi"],
"libs-windows-x86_64": ["iphlpapi"]
}

13
meta/dub.json Normal file
View File

@ -0,0 +1,13 @@
{
"name": "meta",
"description": "Template metaprogramming",
"targetType": "library",
"sourcePaths": [
"."
],
"importPaths": [
"."
],
"dflags-dmd": ["-dip1000"]
}

File diff suppressed because it is too large Load Diff

View File

@ -5,11 +5,15 @@
/**
* Template metaprogramming.
*
* Copyright: Eugene Wissner 2017.
* This package contains utilities to acquire type information at compile-time,
* to transform from one type to another. It has also different algorithms for
* iterating, searching and modifying template arguments.
*
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/meta/package.d,
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/meta/tanya/meta/package.d,
* tanya/meta/package.d)
*/
module tanya.meta;

3048
meta/tanya/meta/trait.d Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,17 +6,19 @@
* Type transformations.
*
* Templates in this module can be used to modify type qualifiers or transform
* types.
* types. They take some type as argument and return a different type after
* perfoming the specified transformation.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/meta/transform.d,
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/meta/tanya/meta/transform.d,
* tanya/meta/transform.d)
*/
module tanya.meta.transform;
import tanya.meta.metafunction;
import tanya.meta.trait;
/**
@ -41,14 +43,14 @@ import tanya.meta.trait;
*/
template Unqual(T)
{
static if (is(T U == const U)
static if (is(T U == shared const U)
|| is(T U == shared inout U)
|| is(T U == shared inout const U)
|| is(T U == inout const U)
|| is(T U == const U)
|| is(T U == immutable U)
|| is(T U == inout U)
|| is(T U == inout const U)
|| is(T U == shared U)
|| is(T U == shared const U)
|| is(T U == shared inout U)
|| is(T U == shared inout const U))
|| is(T U == shared U))
{
alias Unqual = U;
}
@ -59,7 +61,7 @@ template Unqual(T)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
static assert(is(Unqual!bool == bool));
static assert(is(Unqual!(immutable bool) == bool));
@ -94,7 +96,7 @@ template OriginalType(T)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
enum E1 : const(int)
{
@ -136,8 +138,6 @@ pure nothrow @safe @nogc unittest
* To = Target type.
*
* Returns: $(D_PARAM To) with the constness of $(D_PARAM From).
*
* See_Also: $(D_PSYMBOL CopyTypeQualifiers).
*/
template CopyConstness(From, To)
{
@ -165,7 +165,7 @@ template CopyConstness(From, To)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
static assert(is(CopyConstness!(int, char) == char));
static assert(is(CopyConstness!(const int, char) == const char));
@ -183,187 +183,6 @@ pure nothrow @safe @nogc unittest
static assert(is(CopyConstness!(immutable int, const char) == immutable char));
}
/**
* Copies type qualifiers of $(D_PARAM From) to $(D_PARAM To).
*
* Type qualifiers copied are:
* $(UL
* $(LI const)
* $(LI immutable)
* $(LI inout)
* $(LI shared)
* )
* and combinations of these.
*
* Params:
* From = Source type.
* To = Target type.
*
* Returns: $(D_PARAM To) with the type qualifiers of $(D_PARAM From).
*
* See_Also: $(D_PSYMBOL CopyConstness).
*/
template CopyTypeQualifiers(From, To)
{
static if (is(From T == immutable T))
{
alias CopyTypeQualifiers = immutable To;
}
else static if (is(From T == const T))
{
alias CopyTypeQualifiers = const To;
}
else static if (is(From T == shared T))
{
alias CopyTypeQualifiers = shared To;
}
else static if (is(From T == shared const T))
{
alias CopyTypeQualifiers = shared const To;
}
else static if (is(From T == inout T))
{
alias CopyTypeQualifiers = inout To;
}
else static if (is(From T == shared inout T))
{
alias CopyTypeQualifiers = shared inout To;
}
else static if (is(From T == inout const T))
{
alias CopyTypeQualifiers = inout const To;
}
else static if (is(From T == shared inout const T))
{
alias CopyTypeQualifiers = shared inout const To;
}
else
{
alias CopyTypeQualifiers = To;
}
}
///
pure nothrow @safe @nogc unittest
{
static assert(is(CopyTypeQualifiers!(int, char) == char));
static assert(is(CopyTypeQualifiers!(const int, char) == const char));
static assert(is(CopyTypeQualifiers!(immutable int, char) == immutable char));
static assert(is(CopyTypeQualifiers!(inout int, char) == inout char));
static assert(is(CopyTypeQualifiers!(inout const int, char) == inout const char));
static assert(is(CopyTypeQualifiers!(shared int, char) == shared char));
static assert(is(CopyTypeQualifiers!(shared const int, char) == shared const char));
static assert(is(CopyTypeQualifiers!(shared inout int, char) == shared inout char));
static assert(is(CopyTypeQualifiers!(shared inout const int, char) == shared inout const char));
}
/**
* Evaluates to the unsigned counterpart of the integral type $(D_PARAM T) preserving all type qualifiers.
* If $(D_PARAM T) is already unsigned, $(D_INLINECODE Unsigned!T) aliases $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: Unsigned counterpart of $(D_PARAM T).
*
* See_Also: $(D_PSYMBOL isSigned).
*/
template Unsigned(T)
if (isIntegral!T)
{
alias UnqualedType = Unqual!(OriginalType!T);
static if (is(UnqualedType == byte))
{
alias Unsigned = CopyTypeQualifiers!(T, ubyte);
}
else static if (is(UnqualedType == short))
{
alias Unsigned = CopyTypeQualifiers!(T, ushort);
}
else static if (is(UnqualedType == int))
{
alias Unsigned = CopyTypeQualifiers!(T, uint);
}
else static if (is(UnqualedType == long))
{
alias Unsigned = CopyTypeQualifiers!(T, ulong);
}
else
{
alias Unsigned = T;
}
}
///
pure nothrow @safe @nogc unittest
{
static assert(is(Unsigned!byte == ubyte));
static assert(is(Unsigned!short == ushort));
static assert(is(Unsigned!int == uint));
static assert(is(Unsigned!long == ulong));
static assert(is(Unsigned!(const byte) == const ubyte));
static assert(is(Unsigned!(shared byte) == shared ubyte));
static assert(is(Unsigned!(shared const byte) == shared const ubyte));
static assert(!is(Unsigned!float));
static assert(is(Unsigned!ubyte == ubyte));
}
/**
* Evaluates to the signed counterpart of the integral type $(D_PARAM T) preserving all type qualifiers.
* If $(D_PARAM T) is already signed, $(D_INLINECODE Signed!T) aliases $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: Signed counterpart of $(D_PARAM T).
*
* See_Also: $(D_PSYMBOL isUnsigned).
*/
template Signed(T)
if (isIntegral!T)
{
alias UnqualedType = Unqual!(OriginalType!T);
static if (is(UnqualedType == ubyte))
{
alias Signed = CopyTypeQualifiers!(T, byte);
}
else static if (is(UnqualedType == ushort))
{
alias Signed = CopyTypeQualifiers!(T, short);
}
else static if (is(UnqualedType == uint))
{
alias Signed = CopyTypeQualifiers!(T, int);
}
else static if (is(UnqualedType == ulong))
{
alias Signed = CopyTypeQualifiers!(T, long);
}
else
{
alias Signed = T;
}
}
///
pure nothrow @safe @nogc unittest
{
static assert(is(Signed!ubyte == byte));
static assert(is(Signed!ushort == short));
static assert(is(Signed!uint == int));
static assert(is(Signed!ulong == long));
static assert(is(Signed!(const ubyte) == const byte));
static assert(is(Signed!(shared ubyte) == shared byte));
static assert(is(Signed!(shared const ubyte) == shared const byte));
static assert(!is(Signed!float));
static assert(is(Signed!byte == byte));
}
/**
* Retrieves the target type `U` of a pointer `U*`.
*
@ -385,7 +204,7 @@ template PointerTarget(T)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
static assert(is(PointerTarget!(bool*) == bool));
static assert(is(PointerTarget!(const bool*) == const bool));
@ -412,7 +231,7 @@ template KeyType(T)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
static assert(is(KeyType!(int[string]) == string));
static assert(!is(KeyType!(int[15])));
@ -437,39 +256,249 @@ template ValueType(T)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
static assert(is(ValueType!(int[string]) == int));
static assert(!is(ValueType!(int[15])));
}
/**
* Adds $(D_KEYWORD inout) qualifier to the type $(D_PARAM T).
*
* Params:
* T = Scalar type.
* T = A type.
*
* Returns: The type $(D_PARAM T) will promote to.
*
* See_Also: $(LINK2 https://dlang.org/spec/type.html#integer-promotions,
* Integer Promotions).
* Returns: $(D_INLINECODE inout(T)).
*/
template Promoted(T)
if (isScalarType!T)
alias InoutOf(T) = inout(T);
///
@nogc nothrow pure @safe unittest
{
alias Promoted = CopyTypeQualifiers!(T, typeof(T.init + T.init));
static assert(is(InoutOf!int == inout int));
}
/**
* Adds $(D_KEYWORD inout) qualifier to the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE inout(T)).
*/
alias ConstOf(T) = const(T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(ConstOf!int == const int));
}
/**
* Adds $(D_KEYWORD inout) qualifier to the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE inout(T)).
*/
alias SharedOf(T) = shared(T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(SharedOf!int == shared int));
}
/**
* Adds $(D_KEYWORD inout) qualifier to the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE inout(T)).
*/
alias SharedInoutOf(T) = shared(inout T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(SharedInoutOf!int == shared inout int));
}
/**
* Adds $(D_KEYWORD shared const) qualifier to the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE shared(const T)).
*/
alias SharedConstOf(T) = shared(const T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(SharedConstOf!int == shared const int));
}
/**
* Adds $(D_KEYWORD immutable) qualifier to the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE immutable(T)).
*/
alias ImmutableOf(T) = immutable(T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(ImmutableOf!int == immutable int));
}
/**
* Adds $(D_KEYWORD inout const) qualifier to the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE inout(const T)).
*/
alias InoutConstOf(T) = inout(const T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(InoutConstOf!int == inout const int));
}
/**
* Adds $(D_KEYWORD shared inout const) qualifier to the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE shared(inout const T)).
*/
alias SharedInoutConstOf(T) = shared(inout const T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(SharedInoutConstOf!int == shared inout const int));
}
/**
* Determines the type of $(D_PARAM T). If $(D_PARAM T) is already a type,
* $(D_PSYMBOL TypeOf) aliases itself to $(D_PARAM T).
*
* $(D_PSYMBOL TypeOf) evaluates to $(D_KEYWORD void) for template arguments.
*
* The symbols that don't have a type and aren't types cannot be used as
* arguments to $(D_PSYMBOL TypeOf).
*
* Params:
* T = Expression, type or template.
*
* Returns: The type of $(D_PARAM T).
*/
alias TypeOf(T) = T;
/// ditto
template TypeOf(alias T)
if (isExpressions!T || __traits(isTemplate, T))
{
alias TypeOf = typeof(T);
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
struct S(T)
{
static assert(is(Promoted!bool == int));
static assert(is(Promoted!byte == int));
static assert(is(Promoted!ubyte == int));
static assert(is(Promoted!short == int));
static assert(is(Promoted!ushort == int));
static assert(is(Promoted!char == int));
static assert(is(Promoted!wchar == int));
static assert(is(Promoted!dchar == uint));
static assert(is(Promoted!(const bool) == const int));
static assert(is(Promoted!(shared bool) == shared int));
}
static assert(is(TypeOf!S == void));
static assert(is(TypeOf!int == int));
static assert(is(TypeOf!true == bool));
static assert(!is(TypeOf!(tanya.meta)));
}
/**
* Finds the type with the smallest size in the $(D_PARAM Args) list. If
* several types have the same type, the leftmost is returned.
*
* Params:
* Args = Type list.
*
* Returns: The smallest type.
*
* See_Also: $(D_PSYMBOL Largest).
*/
template Smallest(Args...)
if (Args.length >= 1)
{
static assert(is(Args[0]), T.stringof ~ " doesn't have .sizeof property");
static if (Args.length == 1)
{
alias Smallest = Args[0];
}
else static if (Smallest!(Args[1 .. $]).sizeof < Args[0].sizeof)
{
alias Smallest = Smallest!(Args[1 .. $]);
}
else
{
alias Smallest = Args[0];
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(is(Smallest!(int, ushort, uint, short) == ushort));
static assert(is(Smallest!(short) == short));
static assert(is(Smallest!(ubyte[8], ubyte[5]) == ubyte[5]));
static assert(!is(Smallest!(short, 5)));
}
/**
* Finds the type with the largest size in the $(D_PARAM Args) list. If several
* types have the same type, the leftmost is returned.
*
* Params:
* Args = Type list.
*
* Returns: The largest type.
*
* See_Also: $(D_PSYMBOL Smallest).
*/
template Largest(Args...)
if (Args.length >= 1)
{
static assert(is(Args[0]), T.stringof ~ " doesn't have .sizeof property");
static if (Args.length == 1)
{
alias Largest = Args[0];
}
else static if (Largest!(Args[1 .. $]).sizeof > Args[0].sizeof)
{
alias Largest = Largest!(Args[1 .. $]);
}
else
{
alias Largest = Args[0];
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(is(Largest!(int, short, uint) == int));
static assert(is(Largest!(short) == short));
static assert(is(Largest!(ubyte[8], ubyte[5]) == ubyte[8]));
static assert(!is(Largest!(short, 5)));
}

22
middle/dub.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "middle",
"description": "Runtime, middle-level utilities",
"targetType": "library",
"dependencies": {
"tanya:meta": "*",
"tanya:os": "*"
},
"dependencies-linux": {
"mir-linux-kernel": "~>1.0.0"
},
"sourcePaths": [
"."
],
"importPaths": [
"."
],
"dflags-dmd": ["-dip1000"]
}

BIN
middle/middle-test-library Executable file

Binary file not shown.

View File

@ -3,25 +3,85 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Dynamic memory management.
* This module contains the interface for implementing custom allocators.
*
* Copyright: Eugene Wissner 2016-2017.
* Allocators are classes encapsulating memory allocation strategy. This allows
* to decouple memory management from the algorithms and the data.
*
* Copyright: Eugene Wissner 2016-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/package.d,
* tanya/memory/package.d)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/allocator.d,
* tanya/memory/allocator.d)
*/
module tanya.memory;
module tanya.memory.allocator;
import core.exception;
import std.algorithm.iteration;
import std.algorithm.mutation;
import std.conv;
import std.range;
import std.traits;
public import tanya.memory.allocator;
import tanya.memory.mmappool;
import tanya.memory.lifetime;
import tanya.meta.trait;
/**
* Abstract class implementing a basic allocator.
*/
interface Allocator
{
/**
* Returns: Alignment offered.
*/
@property uint alignment() const shared pure nothrow @safe @nogc;
/**
* Allocates $(D_PARAM size) bytes of memory.
*
* Params:
* size = Amount of memory to allocate.
*
* Returns: Pointer to the new allocated memory.
*/
void[] allocate(size_t size) shared pure nothrow @nogc;
/**
* Deallocates a memory block.
*
* Params:
* p = A pointer to the memory block to be freed.
*
* Returns: Whether the deallocation was successful.
*/
bool deallocate(void[] p) shared pure nothrow @nogc;
/**
* Increases or decreases the size of a memory block.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: Pointer to the allocated memory.
*/
bool reallocate(ref void[] p, size_t size) shared pure nothrow @nogc;
/**
* Reallocates a memory block in place if possible or returns
* $(D_KEYWORD false). This function cannot be used to allocate or
* deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
* $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
*/
bool reallocateInPlace(ref void[] p, size_t size)
shared pure nothrow @nogc;
}
package template GetPureInstance(T : Allocator)
{
alias GetPureInstance = shared(T) function()
pure nothrow @nogc;
}
/**
* The mixin generates common methods for classes and structs using
@ -40,12 +100,12 @@ mixin template DefaultAllocator()
*
* Precondition: $(D_INLINECODE allocator_ !is null)
*/
this(shared Allocator allocator) pure nothrow @safe @nogc
this(shared Allocator allocator) @nogc nothrow pure @safe
in
{
assert(allocator !is null);
}
body
do
{
this.allocator_ = allocator;
}
@ -58,12 +118,12 @@ mixin template DefaultAllocator()
*
* Postcondition: $(D_INLINECODE allocator !is null)
*/
protected @property shared(Allocator) allocator() pure nothrow @safe @nogc
@property shared(Allocator) allocator() @nogc nothrow pure @safe
out (allocator)
{
assert(allocator !is null);
}
body
do
{
if (allocator_ is null)
{
@ -72,13 +132,13 @@ mixin template DefaultAllocator()
return allocator_;
}
/// Ditto.
@property shared(Allocator) allocator() const pure nothrow @trusted @nogc
/// ditto
@property shared(Allocator) allocator() const @nogc nothrow pure @trusted
out (allocator)
{
assert(allocator !is null);
}
body
do
{
if (allocator_ is null)
{
@ -88,19 +148,23 @@ mixin template DefaultAllocator()
}
}
// From druntime
extern (C)
private void _d_monitordelete(Object h, bool det) pure nothrow @nogc;
shared Allocator allocator;
shared static this() nothrow @nogc
private shared(Allocator) getAllocatorInstance() @nogc nothrow
{
allocator = MmapPool.instance;
if (allocator is null)
{
version (TanyaNative)
{
import tanya.memory.mmappool : MmapPool;
defaultAllocator = MmapPool.instance;
}
private shared(Allocator) getAllocatorInstance() nothrow @nogc
else
{
import tanya.memory.mallocator : Mallocator;
defaultAllocator = Mallocator.instance;
}
}
return allocator;
}
@ -109,12 +173,12 @@ private shared(Allocator) getAllocatorInstance() nothrow @nogc
*
* Postcondition: $(D_INLINECODE allocator !is null).
*/
@property shared(Allocator) defaultAllocator() pure nothrow @trusted @nogc
@property shared(Allocator) defaultAllocator() @nogc nothrow pure @trusted
out (allocator)
{
assert(allocator !is null);
}
body
do
{
return (cast(GetPureInstance!Allocator) &getAllocatorInstance)();
}
@ -127,46 +191,16 @@ body
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
@property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc
@property void defaultAllocator(shared(Allocator) allocator) @nogc nothrow @safe
in
{
assert(allocator !is null);
}
body
do
{
.allocator = allocator;
}
private nothrow @nogc unittest
{
import tanya.memory.mallocator;
auto oldAllocator = defaultAllocator;
defaultAllocator = Mallocator.instance;
assert(defaultAllocator is Mallocator.instance);
defaultAllocator = oldAllocator;
}
/**
* Returns the size in bytes of the state that needs to be allocated to hold an
* object of type $(D_PARAM T).
*
* Params:
* T = Object type.
*/
template stateSize(T)
{
static if (is(T == class) || is(T == interface))
{
enum stateSize = __traits(classInstanceSize, T);
}
else
{
enum stateSize = T.sizeof;
}
}
/**
* Params:
* size = Raw size.
@ -180,71 +214,190 @@ pure nothrow @safe @nogc
return (size - 1) / alignment * alignment + alignment;
}
/*
* Internal function used to create, resize or destroy a dynamic array. It
* may throw $(D_PSYMBOL OutOfMemoryError). The new
* allocated part of the array isn't initialized. This function can be trusted
* only in the data structures that can ensure that the array is
* allocated/rellocated/deallocated with the same allocator.
/**
* Error thrown if memory allocation fails.
*/
final class OutOfMemoryError : Error
{
/**
* Constructs new error.
*
* Params:
* T = Element type of the array being created.
* allocator = The allocator used for getting memory.
* array = A reference to the array being changed.
* length = New array length.
*
* Returns: $(D_PARAM array).
* msg = The message for the exception.
* file = The file where the exception occurred.
* line = The line number where the exception occurred.
* next = The previous exception in the chain of exceptions, if any.
*/
package(tanya) T[] resize(T)(shared Allocator allocator,
auto ref T[] array,
const size_t length) @trusted
this(string msg = "Out of memory",
string file = __FILE__,
size_t line = __LINE__,
Throwable next = null) @nogc nothrow pure @safe
{
if (length == 0)
super(msg, file, line, next);
}
/// ditto
this(string msg,
Throwable next,
string file = __FILE__,
size_t line = __LINE__) @nogc nothrow pure @safe
{
if (allocator.deallocate(array))
super(msg, file, line, next);
}
}
/**
* Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T).
* It is assumed the respective entities had been allocated with the same
* allocator.
*
* Params:
* T = Type of $(D_PARAM p).
* allocator = Allocator the $(D_PARAM p) was allocated with.
* p = Object or array to be destroyed.
*/
void dispose(T)(shared Allocator allocator, auto ref T p)
{
return null;
() @trusted { allocator.deallocate(finalize(p)); }();
p = null;
}
/**
* Constructs a new class instance of type $(D_PARAM T) using $(D_PARAM args)
* as the parameter list for the constructor of $(D_PARAM T).
*
* Params:
* T = Class type.
* A = Types of the arguments to the constructor of $(D_PARAM T).
* allocator = Allocator.
* args = Constructor arguments of $(D_PARAM T).
*
* Returns: Newly created $(D_PSYMBOL T).
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
T make(T, A...)(shared Allocator allocator, auto ref A args)
if (is(T == class))
in
{
assert(allocator !is null);
}
do
{
auto mem = (() @trusted => allocator.allocate(stateSize!T))();
if (mem is null)
{
onOutOfMemoryError();
}
scope (failure)
{
() @trusted { allocator.deallocate(mem); }();
}
return emplace!T(mem[0 .. stateSize!T], args);
}
/**
* Constructs a value object of type $(D_PARAM T) using $(D_PARAM args)
* as the parameter list for the constructor of $(D_PARAM T) and returns a
* pointer to the new object.
*
* Params:
* T = Object type.
* A = Types of the arguments to the constructor of $(D_PARAM T).
* allocator = Allocator.
* args = Constructor arguments of $(D_PARAM T).
*
* Returns: Pointer to the created object.
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
T* make(T, A...)(shared Allocator allocator, auto ref A args)
if (!isPolymorphicType!T && !isAssociativeArray!T && !isArray!T)
in
{
assert(allocator !is null);
}
do
{
auto mem = (() @trusted => allocator.allocate(stateSize!T))();
if (mem is null)
{
onOutOfMemoryError();
}
scope (failure)
{
() @trusted { allocator.deallocate(mem); }();
}
return emplace!T(mem[0 .. stateSize!T], args);
}
///
@nogc nothrow pure @safe unittest
{
int* i = defaultAllocator.make!int(5);
assert(*i == 5);
defaultAllocator.dispose(i);
}
/**
* Constructs a new array with $(D_PARAM n) elements.
*
* Params:
* T = Array type.
* E = Array element type.
* allocator = Allocator.
* n = Array size.
*
* Returns: Newly created array.
*
* Precondition: $(D_INLINECODE allocator !is null
* && n <= size_t.max / E.sizeof)
*/
T make(T : E[], E)(shared Allocator allocator, size_t n)
in
{
assert(allocator !is null);
assert(n <= size_t.max / E.sizeof);
}
do
{
auto ret = allocator.resize!E(null, n);
static if (hasElaborateDestructor!E)
{
for (auto range = ret; range.length != 0; range = range[1 .. $])
{
emplace!E(cast(void[]) range[0 .. 1], E.init);
}
}
else
{
onOutOfMemoryErrorNoGC();
}
ret[] = E.init;
}
void[] buf = array;
if (!allocator.reallocate(buf, length * T.sizeof))
return ret;
}
///
@nogc nothrow pure @safe unittest
{
onOutOfMemoryErrorNoGC();
}
// Casting from void[] is unsafe, but we know we cast to the original type.
array = cast(T[]) buf;
return array;
}
private unittest
{
int[] p;
p = defaultAllocator.resize(p, 20);
assert(p.length == 20);
p = defaultAllocator.resize(p, 30);
assert(p.length == 30);
p = defaultAllocator.resize(p, 10);
assert(p.length == 10);
p = defaultAllocator.resize(p, 0);
assert(p is null);
int[] i = defaultAllocator.make!(int[])(2);
assert(i.length == 2);
assert(i[0] == int.init && i[1] == int.init);
defaultAllocator.dispose(i);
}
/*
* Destroys the object.
* Returns the memory should be freed.
*/
package(tanya) void[] finalize(T)(ref T* p)
package void[] finalize(T)(ref T* p)
{
if (p is null)
{
return null;
}
static if (hasElaborateDestructor!T)
{
destroy(*p);
@ -252,8 +405,8 @@ package(tanya) void[] finalize(T)(ref T* p)
return (cast(void*) p)[0 .. T.sizeof];
}
package(tanya) void[] finalize(T)(ref T p)
if (is(T == class) || is(T == interface))
package void[] finalize(T)(ref T p)
if (isPolymorphicType!T)
{
if (p is null)
{
@ -307,175 +460,70 @@ package(tanya) void[] finalize(T)(ref T p)
return support;
}
package(tanya) void[] finalize(T)(ref T[] p)
package void[] finalize(T)(ref T[] p)
{
static if (hasElaborateDestructor!(typeof(p[0])))
{
p.each!((ref e) => destroy(e));
}
destroyAllImpl!(T[], T)(p);
return p;
}
/**
* Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T).
* It is assumed the respective entities had been allocated with the same
* allocator.
* Allocates $(D_PSYMBOL OutOfMemoryError) in a static storage and throws it.
*
* Params:
* T = Type of $(D_PARAM p).
* allocator = Allocator the $(D_PARAM p) was allocated with.
* p = Object or array to be destroyed.
* msg = Custom error message.
*
* Throws: $(D_PSYMBOL OutOfMemoryError).
*/
void dispose(T)(shared Allocator allocator, auto ref T p)
void onOutOfMemoryError(string msg = "Out of memory")
@nogc nothrow pure @trusted
{
() @trusted { allocator.deallocate(finalize(p)); }();
p = null;
static ubyte[stateSize!OutOfMemoryError] memory;
alias PureType = OutOfMemoryError function(string) @nogc nothrow pure;
throw (cast(PureType) () => emplace!OutOfMemoryError(memory))(msg);
}
private unittest
{
struct S
{
~this()
{
}
}
auto p = cast(S[]) defaultAllocator.allocate(S.sizeof);
// From druntime
extern (C)
private void _d_monitordelete(Object h, bool det) @nogc nothrow pure;
defaultAllocator.dispose(p);
}
// Works with interfaces.
private pure unittest
{
interface I
{
}
class C : I
{
}
auto c = defaultAllocator.make!C();
I i = c;
defaultAllocator.dispose(i);
defaultAllocator.dispose(i);
}
/**
* Constructs a new class instance of type $(D_PARAM T) using $(D_PARAM args)
* as the parameter list for the constructor of $(D_PARAM T).
/*
* Internal function used to create, resize or destroy a dynamic array. It
* may throw $(D_PSYMBOL OutOfMemoryError). The new
* allocated part of the array isn't initialized. This function can be trusted
* only in the data structures that can ensure that the array is
* allocated/rellocated/deallocated with the same allocator.
*
* Params:
* T = Class type.
* A = Types of the arguments to the constructor of $(D_PARAM T).
* allocator = Allocator.
* args = Constructor arguments of $(D_PARAM T).
* T = Element type of the array being created.
* allocator = The allocator used for getting memory.
* array = A reference to the array being changed.
* length = New array length.
*
* Returns: Newly created $(D_PSYMBOL T).
*
* Precondition: $(D_INLINECODE allocator !is null)
* Returns: $(D_PARAM array).
*/
T make(T, A...)(shared Allocator allocator, auto ref A args)
if (is(T == class))
in
package(tanya) T[] resize(T)(shared Allocator allocator,
auto ref T[] array,
const size_t length) @trusted
{
assert(allocator !is null);
if (length == 0)
{
if (allocator.deallocate(array))
{
return null;
}
body
{
auto mem = (() @trusted => allocator.allocate(stateSize!T))();
if (mem is null)
else
{
onOutOfMemoryError();
}
scope (failure)
{
() @trusted { allocator.deallocate(mem); }();
}
return emplace!T(mem[0 .. stateSize!T], args);
}
/**
* Constructs a value object of type $(D_PARAM T) using $(D_PARAM args)
* as the parameter list for the constructor of $(D_PARAM T) and returns a
* pointer to the new object.
*
* Params:
* T = Object type.
* A = Types of the arguments to the constructor of $(D_PARAM T).
* allocator = Allocator.
* args = Constructor arguments of $(D_PARAM T).
*
* Returns: Pointer to the created object.
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
T* make(T, A...)(shared Allocator allocator, auto ref A args)
if (!is(T == interface)
&& !is(T == class)
&& !isAssociativeArray!T
&& !isArray!T)
in
{
assert(allocator !is null);
}
body
{
auto mem = (() @trusted => allocator.allocate(stateSize!T))();
if (mem is null)
void[] buf = array;
if (!allocator.reallocate(buf, length * T.sizeof))
{
onOutOfMemoryError();
}
scope (failure)
{
() @trusted { allocator.deallocate(mem); }();
}
// Casting from void[] is unsafe, but we know we cast to the original type.
array = cast(T[]) buf;
auto ptr = (() @trusted => (cast(T*) mem[0 .. stateSize!T].ptr))();
return emplace!T(ptr, args);
}
///
unittest
{
int* i = defaultAllocator.make!int(5);
assert(*i == 5);
defaultAllocator.dispose(i);
}
/**
* Constructs a new array with $(D_PARAM n) elements.
*
* Params:
* T = Array type.
* allocator = Allocator.
* n = Array size.
*
* Returns: Newly created array.
*
* Precondition: $(D_INLINECODE allocator !is null
* && n <= size_t.max / ElementType!T.sizeof)
*/
T make(T)(shared Allocator allocator, const size_t n)
if (isArray!T)
in
{
assert(allocator !is null);
assert(n <= size_t.max / ElementType!T.sizeof);
}
body
{
auto ret = allocator.resize!(ElementType!T)(null, n);
ret.uninitializedFill(ElementType!T.init);
return ret;
}
///
unittest
{
int[] i = defaultAllocator.make!(int[])(2);
assert(i.length == 2);
assert(i[0] == int.init && i[1] == int.init);
defaultAllocator.dispose(i);
return array;
}

View File

@ -0,0 +1,540 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Lifetime management functions, types and related exceptions.
*
* Copyright: Eugene Wissner 2019-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/lifetime.d,
* tanya/memory/lifetime.d)
*/
module tanya.memory.lifetime;
import tanya.memory.allocator;
import tanya.meta.metafunction;
import tanya.meta.trait;
package(tanya) void destroyAllImpl(R, E)(R p)
{
static if (hasElaborateDestructor!E)
{
foreach (ref e; p)
{
destroy(e);
}
}
}
/**
* Constructs a new object of type $(D_PARAM T) in $(D_PARAM memory) with the
* given arguments.
*
* If $(D_PARAM T) is a $(D_KEYWORD class), emplace returns a class reference
* of type $(D_PARAM T), otherwise a pointer to the constructed object is
* returned.
*
* If $(D_PARAM T) is a nested class inside another class, $(D_PARAM outer)
* should be an instance of the outer class.
*
* $(D_PARAM args) are arguments for the constructor of $(D_PARAM T). If
* $(D_PARAM T) isn't an aggregate type and doesn't have a constructor,
* $(D_PARAM memory) can be initialized to `args[0]` if `Args.length == 1`,
* `Args[0]` should be implicitly convertible to $(D_PARAM T) then.
*
* Params:
* T = Constructed type.
* U = Type of the outer class if $(D_PARAM T) is a nested class.
* Args = Types of the constructor arguments if $(D_PARAM T) has a constructor
* or the type of the initial value.
* outer = Outer class instance if $(D_PARAM T) is a nested class.
* args = Constructor arguments if $(D_PARAM T) has a constructor or the
* initial value.
*
* Returns: New instance of type $(D_PARAM T) constructed in $(D_PARAM memory).
*
* Precondition: `memory.length == stateSize!T`.
* Postcondition: $(D_PARAM memory) and the result point to the same memory.
*/
T emplace(T, U, Args...)(void[] memory, U outer, auto ref Args args)
if (!isAbstractClass!T && isInnerClass!T && is(typeof(T.outer) == U))
in
{
assert(memory.length >= stateSize!T);
}
out (result)
{
assert(memory.ptr is (() @trusted => cast(void*) result)());
}
do
{
import tanya.memory.op : copy;
copy(typeid(T).initializer, memory);
auto result = (() @trusted => cast(T) memory.ptr)();
result.outer = outer;
static if (is(typeof(result.__ctor(args))))
{
result.__ctor(args);
}
return result;
}
/// ditto
T emplace(T, Args...)(void[] memory, auto ref Args args)
if (is(T == class) && !isAbstractClass!T && !isInnerClass!T)
in
{
assert(memory.length == stateSize!T);
}
out (result)
{
assert(memory.ptr is (() @trusted => cast(void*) result)());
}
do
{
import tanya.memory.op : copy;
copy(typeid(T).initializer, memory);
auto result = (() @trusted => cast(T) memory.ptr)();
static if (is(typeof(result.__ctor(args))))
{
result.__ctor(args);
}
return result;
}
///
@nogc nothrow pure @safe unittest
{
class C
{
int i = 5;
class Inner
{
int i;
this(int param) pure nothrow @safe @nogc
{
this.i = param;
}
}
}
ubyte[stateSize!C] memory1;
ubyte[stateSize!(C.Inner)] memory2;
auto c = emplace!C(memory1);
assert(c.i == 5);
auto inner = emplace!(C.Inner)(memory2, c, 8);
assert(c.i == 5);
assert(inner.i == 8);
assert(inner.outer is c);
}
/// ditto
T* emplace(T, Args...)(void[] memory, auto ref Args args)
if (!isAggregateType!T && (Args.length <= 1))
in
{
assert(memory.length >= T.sizeof);
}
out (result)
{
assert(memory.ptr is result);
}
do
{
auto result = (() @trusted => cast(T*) memory.ptr)();
static if (Args.length == 1)
{
*result = T(args[0]);
}
else
{
*result = T.init;
}
return result;
}
private void initializeOne(T)(ref void[] memory, ref T* result) @trusted
{
import tanya.memory.op : copy, fill;
static if (!hasElaborateAssign!T && isAssignable!T)
{
*result = T.init;
}
else static if (__VERSION__ >= 2083 // __traits(isZeroInit) available.
&& __traits(isZeroInit, T))
{
memory.ptr[0 .. T.sizeof].fill!0;
}
else
{
static immutable T init = T.init;
copy((&init)[0 .. 1], memory);
}
}
/// ditto
T* emplace(T, Args...)(void[] memory, auto ref Args args)
if (!isPolymorphicType!T && isAggregateType!T)
in
{
assert(memory.length >= T.sizeof);
}
out (result)
{
assert(memory.ptr is result);
}
do
{
auto result = (() @trusted => cast(T*) memory.ptr)();
static if (Args.length == 0)
{
static assert(is(typeof({ static T t; })),
"Default constructor is disabled");
initializeOne(memory, result);
}
else static if (is(typeof(result.__ctor(args))))
{
initializeOne(memory, result);
result.__ctor(args);
}
else static if (Args.length == 1 && is(typeof({ T t = args[0]; })))
{
import tanya.memory.op : copy;
((ref arg) @trusted =>
copy((cast(void*) &arg)[0 .. T.sizeof], memory))(args[0]);
static if (hasElaborateCopyConstructor!T)
{
result.__postblit();
}
}
else static if (is(typeof({ T t = T(args); })))
{
auto init = T(args);
(() @trusted => moveEmplace(init, *result))();
}
else
{
static assert(false,
"Unable to construct value with the given arguments");
}
return result;
}
///
@nogc nothrow pure @safe unittest
{
ubyte[4] memory;
auto i = emplace!int(memory);
static assert(is(typeof(i) == int*));
assert(*i == 0);
i = emplace!int(memory, 5);
assert(*i == 5);
static struct S
{
int i;
@disable this();
@disable this(this);
this(int i) @nogc nothrow pure @safe
{
this.i = i;
}
}
auto s = emplace!S(memory, 8);
static assert(is(typeof(s) == S*));
assert(s.i == 8);
}
private void deinitialize(bool zero, T)(ref T value)
{
static if (is(T == U[S], U, size_t S))
{
foreach (ref e; value)
{
deinitialize!zero(e);
}
}
else
{
import tanya.memory.op : copy, fill;
static if (isNested!T)
{
// Don't override the context pointer.
enum size_t size = T.sizeof - (void*).sizeof;
}
else
{
enum size_t size = T.sizeof;
}
static if (zero)
{
fill!0((cast(void*) &value)[0 .. size]);
}
else
{
copy(typeid(T).initializer()[0 .. size], (&value)[0 .. 1]);
}
}
}
/**
* Moves $(D_PARAM source) into $(D_PARAM target) assuming that
* $(D_PARAM target) isn't initialized.
*
* Moving the $(D_PARAM source) copies it into the $(D_PARAM target) and places
* the $(D_PARAM source) into a valid but unspecified state, which means that
* after moving $(D_PARAM source) can be destroyed or assigned a new value, but
* accessing it yields an unspecified value. No postblits or destructors are
* called. If the $(D_PARAM target) should be destroyed before, use
* $(D_PSYMBOL move).
*
* $(D_PARAM source) and $(D_PARAM target) must be different objects.
*
* Params:
* T = Object type.
* source = Source object.
* target = Target object.
*
* See_Also: $(D_PSYMBOL move),
* $(D_PSYMBOL hasElaborateCopyConstructor),
* $(D_PSYMBOL hasElaborateDestructor).
*
* Precondition: `&source !is &target`.
*/
void moveEmplace(T)(ref T source, ref T target) @system
in
{
assert(&source !is &target, "Source and target must be different");
}
do
{
static if (is(T == struct) || isStaticArray!T)
{
import tanya.memory.op : copy;
copy((&source)[0 .. 1], (&target)[0 .. 1]);
static if (hasElaborateCopyConstructor!T || hasElaborateDestructor!T)
{
static if (__VERSION__ >= 2083) // __traits(isZeroInit) available.
{
deinitialize!(__traits(isZeroInit, T))(source);
}
else
{
if (typeid(T).initializer().ptr is null)
{
deinitialize!true(source);
}
else
{
deinitialize!false(source);
}
}
}
}
else
{
target = source;
}
}
///
@nogc nothrow pure @system unittest
{
static struct S
{
int member = 5;
this(this) @nogc nothrow pure @safe
{
assert(false);
}
}
S source, target = void;
moveEmplace(source, target);
assert(target.member == 5);
int x1 = 5, x2;
moveEmplace(x1, x2);
assert(x2 == 5);
}
/**
* Moves $(D_PARAM source) into $(D_PARAM target) assuming that
* $(D_PARAM target) isn't initialized.
*
* Moving the $(D_PARAM source) copies it into the $(D_PARAM target) and places
* the $(D_PARAM source) into a valid but unspecified state, which means that
* after moving $(D_PARAM source) can be destroyed or assigned a new value, but
* accessing it yields an unspecified value. $(D_PARAM target) is destroyed before
* the new value is assigned. If $(D_PARAM target) isn't initialized and
* therefore shouldn't be destroyed, $(D_PSYMBOL moveEmplace) can be used.
*
* If $(D_PARAM target) isn't specified, $(D_PSYMBOL move) returns the source
* as rvalue without calling its copy constructor or destructor.
*
* $(D_PARAM source) and $(D_PARAM target) are the same object,
* $(D_PSYMBOL move) does nothing.
*
* Params:
* T = Object type.
* source = Source object.
* target = Target object.
*
* See_Also: $(D_PSYMBOL moveEmplace).
*/
void move(T)(ref T source, ref T target)
{
if ((() @trusted => &source is &target)())
{
return;
}
static if (hasElaborateDestructor!T)
{
target.__xdtor();
}
(() @trusted => moveEmplace(source, target))();
}
/// ditto
T move(T)(ref T source) @trusted
{
static if (hasElaborateCopyConstructor!T || hasElaborateDestructor!T)
{
T target = void;
moveEmplace(source, target);
return target;
}
else
{
return source;
}
}
///
@nogc nothrow pure @safe unittest
{
static struct S
{
int member = 5;
this(this) @nogc nothrow pure @safe
{
assert(false);
}
}
S source, target = void;
move(source, target);
assert(target.member == 5);
assert(move(target).member == 5);
int x1 = 5, x2;
move(x1, x2);
assert(x2 == 5);
assert(move(x2) == 5);
}
/**
* Exchanges the values of $(D_PARAM a) and $(D_PARAM b).
*
* $(D_PSYMBOL swap) moves the contents of $(D_PARAM a) and $(D_PARAM b)
* without calling its postblits or destructors.
*
* Params:
* a = The first object.
* b = The second object.
*/
void swap(T)(ref T a, ref T b) @trusted
{
T tmp = void;
moveEmplace(a, tmp);
moveEmplace(b, a);
moveEmplace(tmp, b);
}
///
@nogc nothrow pure @safe unittest
{
int a = 3, b = 5;
swap(a, b);
assert(a == 5);
assert(b == 3);
}
/**
* Forwards its argument list preserving $(D_KEYWORD ref) and $(D_KEYWORD out)
* storage classes.
*
* $(D_PSYMBOL forward) accepts a list of variables or literals. It returns an
* argument list of the same length that can be for example passed to a
* function accepting the arguments of this type.
*
* Params:
* args = Argument list.
*
* Returns: $(D_PARAM args) with their original storage classes.
*/
template forward(args...)
{
static if (args.length == 0)
{
alias forward = AliasSeq!();
}
else static if (__traits(isRef, args[0]) || __traits(isOut, args[0]))
{
static if (args.length == 1)
{
alias forward = args[0];
}
else
{
alias forward = AliasSeq!(args[0], forward!(args[1 .. $]));
}
}
else
{
@property auto forwardOne()
{
return move(args[0]);
}
static if (args.length == 1)
{
alias forward = forwardOne;
}
else
{
alias forward = AliasSeq!(forwardOne, forward!(args[1 .. $]));
}
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(is(typeof((int i) { int v = forward!i; })));
static assert(is(typeof((ref int i) { int v = forward!i; })));
static assert(is(typeof({
void f(int i, ref int j, out int k)
{
f(forward!(i, j, k));
}
})));
}

View File

@ -3,13 +3,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Allocator based on $(D_PSYMBOL malloc), $(D_PSYMBOL realloc) and $(D_PSYMBOL free).
* Allocator based on $(D_PSYMBOL malloc), $(D_PSYMBOL realloc) and
* $(D_PSYMBOL free).
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/mallocator.d,
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/mallocator.d,
* tanya/memory/mallocator.d)
*/
module tanya.memory.mallocator;
@ -24,11 +25,11 @@ import tanya.memory.allocator;
final class Mallocator : Allocator
{
private alias MallocType = extern (C) void* function(size_t)
pure nothrow @system @nogc;
@nogc nothrow pure @system;
private alias FreeType = extern (C) void function(void*)
pure nothrow @system @nogc;
@nogc nothrow pure @system;
private alias ReallocType = extern (C) void* function(void*, size_t)
pure nothrow @system @nogc;
@nogc nothrow pure @system;
/**
* Allocates $(D_PARAM size) bytes of memory.
@ -38,7 +39,7 @@ final class Mallocator : Allocator
*
* Returns: The pointer to the new allocated memory.
*/
void[] allocate(const size_t size) shared pure nothrow @nogc
void[] allocate(size_t size) @nogc nothrow pure shared @system
{
if (size == 0)
{
@ -50,7 +51,7 @@ final class Mallocator : Allocator
}
///
@nogc nothrow unittest
@nogc nothrow pure @system unittest
{
auto p = Mallocator.instance.allocate(20);
assert(p.length == 20);
@ -68,7 +69,7 @@ final class Mallocator : Allocator
*
* Returns: Whether the deallocation was successful.
*/
bool deallocate(void[] p) shared pure nothrow @nogc
bool deallocate(void[] p) @nogc nothrow pure shared @system
{
if (p !is null)
{
@ -78,7 +79,7 @@ final class Mallocator : Allocator
}
///
@nogc nothrow unittest
@nogc nothrow pure @system unittest
{
void[] p;
assert(Mallocator.instance.deallocate(p));
@ -96,14 +97,15 @@ final class Mallocator : Allocator
*
* Returns: $(D_KEYWORD false).
*/
bool reallocateInPlace(ref void[] p, const size_t size)
shared pure nothrow @nogc
bool reallocateInPlace(ref void[] p, size_t size)
@nogc nothrow pure shared @system
{
cast(void) size;
return false;
}
///
@nogc nothrow unittest
@nogc nothrow pure @system unittest
{
void[] p;
assert(!Mallocator.instance.reallocateInPlace(p, 8));
@ -118,7 +120,8 @@ final class Mallocator : Allocator
*
* Returns: Whether the reallocation was successful.
*/
bool reallocate(ref void[] p, const size_t size) shared pure nothrow @nogc
bool reallocate(ref void[] p, size_t size)
@nogc nothrow pure shared @system
{
if (size == 0)
{
@ -147,7 +150,7 @@ final class Mallocator : Allocator
}
///
@nogc nothrow unittest
@nogc nothrow pure @system unittest
{
void[] p;
@ -164,30 +167,15 @@ final class Mallocator : Allocator
assert(p is null);
}
// Fails with false.
private @nogc nothrow unittest
{
void[] p = Mallocator.instance.allocate(20);
void[] oldP = p;
assert(!Mallocator.instance.reallocate(p, size_t.max - Mallocator.psize * 2));
assert(oldP is p);
Mallocator.instance.deallocate(p);
}
/**
* Returns: The alignment offered.
*/
@property uint alignment() shared const pure nothrow @safe @nogc
@property uint alignment() const @nogc nothrow pure @safe shared
{
return (void*).alignof;
}
private nothrow @nogc unittest
{
assert(Mallocator.instance.alignment == (void*).alignof);
}
static private shared(Mallocator) instantiate() nothrow @nogc
static private shared(Mallocator) instantiate() @nogc nothrow @system
{
if (instance_ is null)
{
@ -208,13 +196,13 @@ final class Mallocator : Allocator
*
* Returns: The global $(D_PSYMBOL Allocator) instance.
*/
static @property shared(Mallocator) instance() pure nothrow @nogc
static @property shared(Mallocator) instance() @nogc nothrow pure @system
{
return (cast(GetPureInstance!Mallocator) &instantiate)();
}
///
@nogc nothrow unittest
@nogc nothrow pure @system unittest
{
assert(instance is instance);
}

View File

@ -2,84 +2,32 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Native allocator for Posix and Windows.
/*
* Native allocator.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/mmappool.d,
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/mmappool.d,
* tanya/memory/mmappool.d)
*/
module tanya.memory.mmappool;
import core.stdc.string;
import std.algorithm.comparison;
import core.sys.linux.sys.mman;
import tanya.memory.allocator;
import tanya.memory.op;
import tanya.os.error;
version (Posix)
{
import core.sys.posix.sys.mman : PROT_READ, PROT_WRITE, MAP_PRIVATE,
MAP_ANON, MAP_FAILED;
import core.sys.posix.unistd;
extern(C) pragma(mangle, "mmap")
private void* mapMemory(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
@nogc nothrow pure @system;
extern (C)
private void* mmap(void* addr,
size_t len,
int prot,
int flags,
int fd,
off_t offset) pure nothrow @system @nogc;
extern(C) pragma(mangle, "munmap")
private bool unmapMemory(shared void* addr, size_t length)
@nogc nothrow pure @system;
extern (C)
private int munmap(void* addr, size_t len) pure nothrow @system @nogc;
private void* mapMemory(const size_t len) pure nothrow @system @nogc
{
void* p = mmap(null,
len,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0);
return p is MAP_FAILED ? null : p;
}
private bool unmapMemory(shared void* addr, const size_t len)
pure nothrow @system @nogc
{
return munmap(cast(void*) addr, len) == 0;
}
}
else version (Windows)
{
import core.sys.windows.winbase : GetSystemInfo, SYSTEM_INFO;
extern (Windows)
private void* VirtualAlloc(void*, size_t, uint, uint)
pure nothrow @system @nogc;
extern (Windows)
private int VirtualFree(void* addr, size_t len, uint)
pure nothrow @system @nogc;
private void* mapMemory(const size_t len) pure nothrow @system @nogc
{
return VirtualAlloc(null,
len,
0x00001000, // MEM_COMMIT
0x04); // PAGE_READWRITE
}
private bool unmapMemory(shared void* addr, const size_t len)
pure nothrow @system @nogc
{
return VirtualFree(cast(void*) addr, 0, 0x8000) == 0;
}
}
/**
/*
* This allocator allocates memory in regions (multiple of 64 KB for example).
* Each region is then splitted in blocks. So it doesn't request the memory
* from the operating system on each call, but only if there are no large
@ -89,6 +37,7 @@ else version (Windows)
* block as free and only if all blocks in the region are free, the complete
* region is deallocated.
*
* <pre>
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* | | | | | || | | |
* | |prev <----------- | || | | |
@ -100,12 +49,13 @@ else version (Windows)
* | N | -----------> next| || N | | |
* | | | | | || | | |
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* </pre>
*/
final class MmapPool : Allocator
{
version (none)
{
pure nothrow @nogc invariant
@nogc nothrow pure @system invariant
{
for (auto r = &head; *r !is null; r = &((*r).next))
{
@ -121,7 +71,7 @@ final class MmapPool : Allocator
}
}
/**
/*
* Allocates $(D_PARAM size) bytes of memory.
*
* Params:
@ -129,7 +79,7 @@ final class MmapPool : Allocator
*
* Returns: Pointer to the new allocated memory.
*/
void[] allocate(const size_t size) shared pure nothrow @nogc
void[] allocate(size_t size) @nogc nothrow pure shared @system
{
if (size == 0)
{
@ -150,35 +100,6 @@ final class MmapPool : Allocator
return data is null ? null : data[0 .. size];
}
///
nothrow unittest
{
auto p = MmapPool.instance.allocate(20);
assert(p);
MmapPool.instance.deallocate(p);
p = MmapPool.instance.allocate(0);
assert(p.length == 0);
}
// Issue 245: https://issues.caraus.io/issues/245.
private @nogc unittest
{
// allocate() check.
size_t tooMuchMemory = size_t.max
- MmapPool.alignment_
- BlockEntry.sizeof * 2
- RegionEntry.sizeof
- MmapPool.instance.pageSize;
assert(MmapPool.instance.allocate(tooMuchMemory) is null);
assert(MmapPool.instance.allocate(size_t.max) is null);
// initializeRegion() check.
tooMuchMemory = size_t.max - MmapPool.alignment_;
assert(MmapPool.instance.allocate(tooMuchMemory) is null);
}
/*
* Search for a block large enough to keep $(D_PARAM size) and split it
* into two blocks if the block is too large.
@ -188,7 +109,8 @@ final class MmapPool : Allocator
*
* Returns: Data the block points to or $(D_KEYWORD null).
*/
private void* findBlock(const ref size_t size) shared pure nothrow @nogc
private void* findBlock(const ref size_t size)
@nogc nothrow pure shared @system
{
Block block1;
RegionLoop: for (auto r = head; r !is null; r = r.next)
@ -230,7 +152,7 @@ final class MmapPool : Allocator
}
// Merge block with the next one.
private void mergeNext(Block block) shared const pure nothrow @safe @nogc
private void mergeNext(Block block) const @nogc nothrow pure @safe shared
{
block.size = block.size + BlockEntry.sizeof + block.next.size;
if (block.next.next !is null)
@ -240,7 +162,7 @@ final class MmapPool : Allocator
block.next = block.next.next;
}
/**
/*
* Deallocates a memory block.
*
* Params:
@ -248,7 +170,7 @@ final class MmapPool : Allocator
*
* Returns: Whether the deallocation was successful.
*/
bool deallocate(void[] p) shared pure nothrow @nogc
bool deallocate(void[] p) @nogc nothrow pure shared @system
{
if (p.ptr is null)
{
@ -270,7 +192,7 @@ final class MmapPool : Allocator
{
block.region.next.prev = block.region.prev;
}
return unmapMemory(block.region, block.region.size);
return unmapMemory(block.region, block.region.size) == 0;
}
// Merge blocks if neigbours are free.
if (block.next !is null && block.next.free)
@ -294,15 +216,7 @@ final class MmapPool : Allocator
return true;
}
///
nothrow unittest
{
auto p = MmapPool.instance.allocate(20);
assert(MmapPool.instance.deallocate(p));
}
/**
/*
* Reallocates a memory block in place if possible or returns
* $(D_KEYWORD false). This function cannot be used to allocate or
* deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
@ -314,8 +228,8 @@ final class MmapPool : Allocator
*
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
*/
bool reallocateInPlace(ref void[] p, const size_t size)
shared pure nothrow @nogc
bool reallocateInPlace(ref void[] p, size_t size)
@nogc nothrow pure shared @system
{
if (p is null || size == 0)
{
@ -345,10 +259,10 @@ final class MmapPool : Allocator
|| dataSize < size
|| block1.next.size + BlockEntry.sizeof < delta)
{
/* * It is the last block in the region
* * The next block is too small
* * The next block isn't free
* * Requested size is too large
/* - It is the last block in the region
* - The next block isn't free
* - The next block is too small
* - Requested size is too large
*/
return false;
}
@ -363,8 +277,8 @@ final class MmapPool : Allocator
{
block1.next.next.prev = block2;
}
// block1.next and block2 can overlap.
memmove(cast(void*) block2, cast(void*) block1.next, BlockEntry.sizeof);
copyBackward((cast(void*) block1.next)[0 .. BlockEntry.sizeof],
(cast(void*) block2)[0 .. BlockEntry.sizeof]);
block1.next = block2;
}
else
@ -378,32 +292,7 @@ final class MmapPool : Allocator
return true;
}
///
nothrow unittest
{
void[] p;
assert(!MmapPool.instance.reallocateInPlace(p, 5));
assert(p is null);
p = MmapPool.instance.allocate(1);
auto orig = p.ptr;
assert(MmapPool.instance.reallocateInPlace(p, 2));
assert(p.length == 2);
assert(p.ptr == orig);
assert(MmapPool.instance.reallocateInPlace(p, 4));
assert(p.length == 4);
assert(p.ptr == orig);
assert(MmapPool.instance.reallocateInPlace(p, 2));
assert(p.length == 2);
assert(p.ptr == orig);
MmapPool.instance.deallocate(p);
}
/**
/*
* Increases or decreases the size of a memory block.
*
* Params:
@ -412,7 +301,8 @@ final class MmapPool : Allocator
*
* Returns: Whether the reallocation was successful.
*/
bool reallocate(ref void[] p, const size_t size) shared pure nothrow @nogc
bool reallocate(ref void[] p, size_t size)
@nogc nothrow pure shared @system
{
if (size == 0)
{
@ -436,7 +326,7 @@ final class MmapPool : Allocator
}
if (p !is null)
{
memcpy(reallocP.ptr, p.ptr, min(p.length, size));
copy(p[0 .. p.length < size ? p.length : size], reallocP);
deallocate(p);
}
p = reallocP;
@ -444,87 +334,35 @@ final class MmapPool : Allocator
return true;
}
///
nothrow unittest
{
void[] p;
MmapPool.instance.reallocate(p, 10 * int.sizeof);
(cast(int[]) p)[7] = 123;
assert(p.length == 40);
MmapPool.instance.reallocate(p, 8 * int.sizeof);
assert(p.length == 32);
assert((cast(int[]) p)[7] == 123);
MmapPool.instance.reallocate(p, 20 * int.sizeof);
(cast(int[]) p)[15] = 8;
assert(p.length == 80);
assert((cast(int[]) p)[15] == 8);
assert((cast(int[]) p)[7] == 123);
MmapPool.instance.reallocate(p, 8 * int.sizeof);
assert(p.length == 32);
assert((cast(int[]) p)[7] == 123);
MmapPool.instance.deallocate(p);
}
static private shared(MmapPool) instantiate() nothrow @nogc
static private shared(MmapPool) instantiate() @nogc nothrow @system
{
if (instance_ is null)
{
// Get system dependend page size.
version (Posix)
{
size_t pageSize = sysconf(_SC_PAGE_SIZE);
if (pageSize < 65536)
{
pageSize = pageSize * 65536 / pageSize;
}
}
else version (Windows)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
size_t pageSize = si.dwPageSize;
}
const instanceSize = addAlignment(__traits(classInstanceSize,
MmapPool));
Region head; // Will become soon our region list head
void* data = initializeRegion(instanceSize, head, pageSize);
void* data = initializeRegion(instanceSize, head);
if (data !is null)
{
memcpy(data, typeid(MmapPool).initializer.ptr, instanceSize);
copy(typeid(MmapPool).initializer, data[0 .. instanceSize]);
instance_ = cast(shared MmapPool) data;
instance_.head = head;
instance_.pageSize = pageSize;
}
}
return instance_;
}
/**
/*
* Static allocator instance and initializer.
*
* Returns: Global $(D_PSYMBOL MmapPool) instance.
*/
static @property shared(MmapPool) instance() pure nothrow @nogc
static @property shared(MmapPool) instance() @nogc nothrow pure @system
{
return (cast(GetPureInstance!MmapPool) &instantiate)();
}
///
nothrow unittest
{
assert(instance is instance);
}
/*
* Initializes a region for one element.
*
@ -534,19 +372,21 @@ final class MmapPool : Allocator
*
* Returns: A pointer to the data.
*/
private static void* initializeRegion(const size_t size,
ref Region head,
const size_t pageSize)
pure nothrow @nogc
private static void* initializeRegion(const size_t size, ref Region head)
@nogc nothrow pure @system
{
const regionSize = calculateRegionSize(size, pageSize);
const regionSize = calculateRegionSize(size);
if (regionSize < size)
{
return null;
}
void* p = mapMemory(regionSize);
if (p is null)
void* p = mapMemory(null,
regionSize,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0);
if (cast(ptrdiff_t) p == -1)
{
return null;
}
@ -586,9 +426,10 @@ final class MmapPool : Allocator
return data;
}
private void* initializeRegion(const size_t size) shared pure nothrow @nogc
private void* initializeRegion(const size_t size)
@nogc nothrow pure shared @system
{
return initializeRegion(size, this.head, this.pageSize);
return initializeRegion(size, this.head);
}
/*
@ -597,7 +438,7 @@ final class MmapPool : Allocator
*
* Returns: Aligned size of $(D_PARAM x).
*/
private static size_t addAlignment(const size_t x) pure nothrow @safe @nogc
private static size_t addAlignment(const size_t x) @nogc nothrow pure @safe
{
return (x - 1) / alignment_ * alignment_ + alignment_;
}
@ -605,35 +446,30 @@ final class MmapPool : Allocator
/*
* Params:
* x = Required space.
* pageSize = Page size.
*
* Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)).
*/
private static size_t calculateRegionSize(ref const size_t x,
ref const size_t pageSize)
pure nothrow @safe @nogc
private static size_t calculateRegionSize(ref const size_t x)
@nogc nothrow pure @safe
{
return (x + RegionEntry.sizeof + BlockEntry.sizeof * 2)
/ pageSize * pageSize + pageSize;
}
/**
/*
* Returns: Alignment offered.
*/
@property uint alignment() shared const pure nothrow @safe @nogc
@property uint alignment() const @nogc nothrow pure @safe shared
{
return alignment_;
}
private nothrow @nogc unittest
{
assert(MmapPool.instance.alignment == MmapPool.alignment_);
}
private enum uint alignment_ = 8;
private shared static MmapPool instance_;
private shared size_t pageSize;
// Page size.
enum size_t pageSize = 65536;
private shared struct RegionEntry
{
@ -656,60 +492,19 @@ final class MmapPool : Allocator
private alias Block = shared BlockEntry*;
}
// A lot of allocations/deallocations, but it is the minimum caused a
// segmentation fault because MmapPool reallocateInPlace moves a block wrong.
private @nogc unittest
@nogc nothrow pure @system unittest
{
auto a = MmapPool.instance.allocate(16);
auto d = MmapPool.instance.allocate(16);
auto b = MmapPool.instance.allocate(16);
auto e = MmapPool.instance.allocate(16);
auto c = MmapPool.instance.allocate(16);
auto f = MmapPool.instance.allocate(16);
// allocate() check.
size_t tooMuchMemory = size_t.max
- MmapPool.alignment_
- MmapPool.BlockEntry.sizeof * 2
- MmapPool.RegionEntry.sizeof
- MmapPool.pageSize;
assert(MmapPool.instance.allocate(tooMuchMemory) is null);
MmapPool.instance.deallocate(a);
MmapPool.instance.deallocate(b);
MmapPool.instance.deallocate(c);
assert(MmapPool.instance.allocate(size_t.max) is null);
a = MmapPool.instance.allocate(50);
MmapPool.instance.reallocateInPlace(a, 64);
MmapPool.instance.deallocate(a);
a = MmapPool.instance.allocate(1);
auto tmp1 = MmapPool.instance.allocate(1);
auto h1 = MmapPool.instance.allocate(1);
auto tmp2 = cast(ubyte[]) MmapPool.instance.allocate(1);
auto h2 = MmapPool.instance.allocate(2);
tmp1 = MmapPool.instance.allocate(1);
MmapPool.instance.deallocate(h2);
MmapPool.instance.deallocate(h1);
h2 = MmapPool.instance.allocate(2);
h1 = MmapPool.instance.allocate(1);
MmapPool.instance.deallocate(h2);
auto rep = cast(void[]) tmp2;
MmapPool.instance.reallocate(rep, tmp1.length);
tmp2 = cast(ubyte[]) rep;
MmapPool.instance.reallocate(tmp1, 9);
rep = cast(void[]) tmp2;
MmapPool.instance.reallocate(rep, tmp1.length);
tmp2 = cast(ubyte[]) rep;
MmapPool.instance.reallocate(tmp1, 17);
tmp2[$ - 1] = 0;
MmapPool.instance.deallocate(tmp1);
b = MmapPool.instance.allocate(16);
MmapPool.instance.deallocate(h1);
MmapPool.instance.deallocate(a);
MmapPool.instance.deallocate(b);
MmapPool.instance.deallocate(d);
MmapPool.instance.deallocate(e);
MmapPool.instance.deallocate(f);
// initializeRegion() check.
tooMuchMemory = size_t.max - MmapPool.alignment_;
assert(MmapPool.instance.allocate(tooMuchMemory) is null);
}

330
middle/tanya/memory/op.d Normal file
View File

@ -0,0 +1,330 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Set of operations on memory blocks.
*
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/op.d,
* tanya/memory/op.d)
*/
module tanya.memory.op;
import core.stdc.string;
private enum alignMask = size_t.sizeof - 1;
/**
* Copies $(D_PARAM source) into $(D_PARAM target).
*
* $(D_PARAM source) and $(D_PARAM target) shall not overlap so that
* $(D_PARAM source) points ahead of $(D_PARAM target).
*
* $(D_PARAM target) shall have enough space for $(D_INLINECODE source.length)
* elements.
*
* Params:
* source = Memory to copy from.
* target = Destination memory.
*
* See_Also: $(D_PSYMBOL copyBackward).
*
* Precondition: $(D_INLINECODE source.length <= target.length).
*/
void copy(const void[] source, void[] target) @nogc nothrow pure @trusted
in
{
assert(source.length <= target.length);
assert(source.length == 0 || source.ptr !is null);
assert(target.length == 0 || target.ptr !is null);
}
do
{
memcpy(target.ptr, source.ptr, source.length);
}
///
@nogc nothrow pure @safe unittest
{
ubyte[9] source = [1, 2, 3, 4, 5, 6, 7, 8, 9];
ubyte[9] target;
source.copy(target);
assert(equal(source, target));
}
/*
* size_t value each of which bytes is set to `Byte`.
*/
private template filledBytes(ubyte Byte, ubyte I = 0)
{
static if (I == size_t.sizeof)
{
enum size_t filledBytes = Byte;
}
else
{
enum size_t filledBytes = (filledBytes!(Byte, I + 1) << 8) | Byte;
}
}
/**
* Fills $(D_PARAM memory) with the single byte $(D_PARAM c).
*
* Param:
* c = The value to fill $(D_PARAM memory) with.
* memory = Memory block.
*/
void fill(ubyte c = 0)(void[] memory) @trusted
in
{
assert(memory.length == 0 || memory.ptr !is null);
}
do
{
memset(memory.ptr, c, memory.length);
}
///
@nogc nothrow pure @safe unittest
{
ubyte[9] memory = [1, 2, 3, 4, 5, 6, 7, 8, 9];
memory.fill!0();
foreach (ubyte v; memory)
{
assert(v == 0);
}
}
/**
* Copies starting from the end of $(D_PARAM source) into the end of
* $(D_PARAM target).
*
* $(D_PSYMBOL copyBackward) copies the elements in reverse order, but the
* order of elements in the $(D_PARAM target) is exactly the same as in the
* $(D_PARAM source).
*
* $(D_PARAM source) and $(D_PARAM target) shall not overlap so that
* $(D_PARAM target) points ahead of $(D_PARAM source).
*
* $(D_PARAM target) shall have enough space for $(D_INLINECODE source.length)
* elements.
*
* Params:
* source = Memory to copy from.
* target = Destination memory.
*
* See_Also: $(D_PSYMBOL copy).
*
* Precondition: $(D_INLINECODE source.length <= target.length).
*/
void copyBackward(const void[] source, void[] target) @nogc nothrow pure @trusted
in
{
assert(source.length <= target.length);
assert(source.length == 0 || source.ptr !is null);
assert(target.length == 0 || target.ptr !is null);
}
do
{
memmove(target.ptr, source.ptr, source.length);
}
///
@nogc nothrow pure @safe unittest
{
ubyte[6] mem = [ 'a', 'a', 'b', 'b', 'c', 'c' ];
ubyte[6] expected = [ 'a', 'a', 'a', 'a', 'b', 'b' ];
copyBackward(mem[0 .. 4], mem[2 .. $]);
assert(equal(expected, mem));
}
/**
* Finds the first occurrence of $(D_PARAM needle) in $(D_PARAM haystack) if
* any.
*
* Params:
* haystack = Memory block.
* needle = A byte.
*
* Returns: The subrange of $(D_PARAM haystack) whose first element is the
* first occurrence of $(D_PARAM needle). If $(D_PARAM needle)
* couldn't be found, an empty `inout void[]` is returned.
*/
inout(void[]) find(return inout void[] haystack, ubyte needle)
@nogc nothrow pure @trusted
in
{
assert(haystack.length == 0 || haystack.ptr !is null);
}
do
{
auto length = haystack.length;
const size_t needleWord = size_t.max * needle;
enum size_t highBits = filledBytes!(0x01, 0);
enum size_t mask = filledBytes!(0x80, 0);
// Align
auto bytes = cast(inout(ubyte)*) haystack;
while (length > 0 && ((cast(size_t) bytes) & 3) != 0)
{
if (*bytes == needle)
{
return bytes[0 .. length];
}
++bytes;
--length;
}
// Check if some of the words has the needle
auto words = cast(inout(size_t)*) bytes;
while (length >= size_t.sizeof)
{
if ((((*words ^ needleWord) - highBits) & (~*words) & mask) != 0)
{
break;
}
++words;
length -= size_t.sizeof;
}
// Find the exact needle position in the word
bytes = cast(inout(ubyte)*) words;
while (length > 0)
{
if (*bytes == needle)
{
return bytes[0 .. length];
}
++bytes;
--length;
}
return haystack[$ .. $];
}
///
@nogc nothrow pure @safe unittest
{
const ubyte[9] haystack = ['a', 'b', 'c', 'd', 'e', 'f', 'b', 'g', 'h'];
assert(equal(find(haystack, 'a'), haystack[]));
assert(equal(find(haystack, 'b'), haystack[1 .. $]));
assert(equal(find(haystack, 'c'), haystack[2 .. $]));
assert(equal(find(haystack, 'd'), haystack[3 .. $]));
assert(equal(find(haystack, 'e'), haystack[4 .. $]));
assert(equal(find(haystack, 'f'), haystack[5 .. $]));
assert(equal(find(haystack, 'h'), haystack[8 .. $]));
assert(find(haystack, 'i').length == 0);
assert(find(null, 'a').length == 0);
}
/**
* Looks for `\0` in the $(D_PARAM haystack) and returns the part of the
* $(D_PARAM haystack) ahead of it.
*
* Returns $(D_KEYWORD null) if $(D_PARAM haystack) doesn't contain a null
* character.
*
* Params:
* haystack = Memory block.
*
* Returns: The subrange that spans all bytes before the null character or
* $(D_KEYWORD null) if the $(D_PARAM haystack) doesn't contain any.
*/
inout(char[]) findNullTerminated(return inout char[] haystack)
@nogc nothrow pure @trusted
in
{
assert(haystack.length == 0 || haystack.ptr !is null);
}
do
{
auto length = haystack.length;
enum size_t highBits = filledBytes!(0x01, 0);
enum size_t mask = filledBytes!(0x80, 0);
// Align
auto bytes = cast(inout(ubyte)*) haystack;
while (length > 0 && ((cast(size_t) bytes) & 3) != 0)
{
if (*bytes == '\0')
{
return haystack[0 .. haystack.length - length];
}
++bytes;
--length;
}
// Check if some of the words contains 0
auto words = cast(inout(size_t)*) bytes;
while (length >= size_t.sizeof)
{
if (((*words - highBits) & (~*words) & mask) != 0)
{
break;
}
++words;
length -= size_t.sizeof;
}
// Find the exact 0 position in the word
bytes = cast(inout(ubyte)*) words;
while (length > 0)
{
if (*bytes == '\0')
{
return haystack[0 .. haystack.length - length];
}
++bytes;
--length;
}
return null;
}
///
@nogc nothrow pure @safe unittest
{
assert(equal(findNullTerminated("abcdef\0gh"), "abcdef"));
assert(equal(findNullTerminated("\0garbage"), ""));
assert(equal(findNullTerminated("\0"), ""));
assert(equal(findNullTerminated("cstring\0"), "cstring"));
assert(findNullTerminated(null) is null);
assert(findNullTerminated("abcdef") is null);
}
/**
* Compares two memory areas $(D_PARAM r1) and $(D_PARAM r2) for equality.
*
* Params:
* r1 = First memory block.
* r2 = Second memory block.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM r1) and $(D_PARAM r2) are equal,
* $(D_KEYWORD false) otherwise.
*/
bool equal(const void[] r1, const void[] r2) @nogc nothrow pure @trusted
in
{
assert(r1.length == 0 || r1.ptr !is null);
assert(r2.length == 0 || r2.ptr !is null);
}
do
{
return r1.length == r2.length && memcmp(r1.ptr, r2.ptr, r1.length) == 0;
}
///
@nogc nothrow pure @safe unittest
{
assert(equal("asdf", "asdf"));
assert(!equal("asd", "asdf"));
assert(!equal("asdf", "asd"));
assert(!equal("asdf", "qwer"));
}

View File

@ -3,15 +3,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This package contains formatting and conversion functions.
* Dynamic memory management.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2016-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/format/package.d,
* tanya/format/package.d)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/package.d,
* tanya/memory/package.d)
*/
module tanya.format;
module tanya.memory;
public import tanya.format.conv;
public import tanya.memory.allocator;
public import tanya.memory.lifetime;

View File

@ -6,28 +6,30 @@
* Smart pointers.
*
* A smart pointer is an object that wraps a raw pointer or a reference
* (class, array) to manage its lifetime.
* (class, dynamic array) to manage its lifetime.
*
* Copyright: Eugene Wissner 2016-2017.
* This module provides two kinds of lifetime management strategies:
* $(UL
* $(LI Reference counting)
* $(LI Unique ownership)
* )
*
* Copyright: Eugene Wissner 2016-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/smartref.d,
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/smartref.d,
* tanya/memory/smartref.d)
*/
module tanya.memory.smartref;
import core.exception;
import std.algorithm.comparison;
import std.algorithm.mutation;
import std.conv;
import std.range;
import std.traits;
import tanya.memory;
import tanya.memory.allocator;
import tanya.memory.lifetime;
import tanya.meta.trait;
private template Payload(T)
{
static if (is(T == class) || is(T == interface) || isArray!T)
static if (isPolymorphicType!T || isDynamicArray!T)
{
alias Payload = T;
}
@ -48,7 +50,7 @@ private final class RefCountedStore(T)
{
assert(this.counter > 0);
}
body
do
{
mixin("return " ~ op ~ "counter;");
}
@ -127,13 +129,13 @@ struct RefCounted(T)
this.storage.payload = value;
}
/// Ditto.
/// ditto
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
do
{
this.allocator_ = allocator;
}
@ -202,14 +204,7 @@ struct RefCounted(T)
return this;
}
private @nogc unittest
{
auto rc = defaultAllocator.refCounted!int(5);
rc = defaultAllocator.make!int(7);
assert(*rc == 7);
}
/// Ditto.
/// ditto
ref typeof(this) opAssign(typeof(null))
{
if (this.storage is null)
@ -229,15 +224,7 @@ struct RefCounted(T)
return this;
}
private @nogc unittest
{
RefCounted!int rc;
assert(!rc.isInitialized);
rc = null;
assert(!rc.isInitialized);
}
/// Ditto.
/// ditto
ref typeof(this) opAssign(typeof(this) rhs)
{
swap(this.allocator_, rhs.allocator_);
@ -256,7 +243,7 @@ struct RefCounted(T)
{
assert(count > 0, "Attempted to access an uninitialized reference");
}
body
do
{
return this.storage.payload;
}
@ -308,165 +295,20 @@ struct RefCounted(T)
}
///
unittest
@nogc @system unittest
{
auto rc = RefCounted!int(defaultAllocator.make!int(5), defaultAllocator);
auto val = rc.get();
*val = 8;
assert(*rc.storage.payload == 8);
assert(*rc.get == 8);
val = null;
assert(rc.storage.payload !is null);
assert(*rc.storage.payload == 8);
assert(rc.get !is null);
assert(*rc.get == 8);
*rc = 9;
assert(*rc.storage.payload == 9);
}
private @nogc unittest
{
auto rc = defaultAllocator.refCounted!int(5);
void func(RefCounted!int param) @nogc
{
assert(param.count == 2);
param = defaultAllocator.make!int(7);
assert(param.count == 1);
assert(*param == 7);
}
func(rc);
assert(rc.count == 1);
assert(*rc == 5);
}
private @nogc unittest
{
RefCounted!int rc;
void func(RefCounted!int param) @nogc
{
assert(param.count == 0);
param = defaultAllocator.make!int(7);
assert(param.count == 1);
assert(*param == 7);
}
func(rc);
assert(rc.count == 0);
}
private unittest
{
RefCounted!int rc1, rc2;
static assert(is(typeof(rc1 = rc2)));
}
version (unittest)
{
private class A
{
uint *destroyed;
this(ref uint destroyed) @nogc
{
this.destroyed = &destroyed;
}
~this() @nogc
{
++(*destroyed);
}
}
private struct B
{
int prop;
@disable this();
this(int param1) @nogc
{
prop = param1;
}
}
}
private @nogc unittest
{
uint destroyed;
auto a = defaultAllocator.make!A(destroyed);
assert(destroyed == 0);
{
auto rc = RefCounted!A(a, defaultAllocator);
assert(rc.count == 1);
void func(RefCounted!A rc) @nogc
{
assert(rc.count == 2);
}
func(rc);
assert(rc.count == 1);
}
assert(destroyed == 1);
RefCounted!int rc;
assert(rc.count == 0);
rc = defaultAllocator.make!int(8);
assert(rc.count == 1);
}
private @nogc unittest
{
auto rc = RefCounted!int(defaultAllocator);
assert(!rc.isInitialized);
assert(rc.allocator is defaultAllocator);
}
private @nogc unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(rc.count == 1);
void func(RefCounted!int rc) @nogc
{
assert(rc.count == 2);
rc = null;
assert(!rc.isInitialized);
assert(rc.count == 0);
}
assert(rc.count == 1);
func(rc);
assert(rc.count == 1);
rc = null;
assert(!rc.isInitialized);
assert(rc.count == 0);
}
private unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(*rc == 5);
void func(RefCounted!int rc) @nogc
{
assert(rc.count == 2);
rc = defaultAllocator.refCounted!int(4);
assert(*rc == 4);
assert(rc.count == 1);
}
func(rc);
assert(*rc == 5);
}
private unittest
{
static assert(is(typeof(RefCounted!int.storage.payload) == int*));
static assert(is(typeof(RefCounted!A.storage.payload) == A));
static assert(is(RefCounted!B));
static assert(is(RefCounted!A));
assert(*rc.get == 9);
}
/**
@ -495,7 +337,7 @@ in
{
assert(allocator !is null);
}
body
do
{
auto rc = typeof(return)(allocator);
@ -511,17 +353,9 @@ body
{
() @trusted { allocator.deallocate(mem); }();
}
rc.storage = emplace!((RefCounted!T.Storage))(mem[0 .. storageSize]);
static if (is(T == class))
{
rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]);
rc.storage.payload = emplace!T(mem[storageSize .. $], args);
}
else
{
auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
rc.storage.payload = emplace!T(ptr, args);
}
rc.deleter = &unifiedDeleter!(Payload!T);
return rc;
}
@ -532,29 +366,29 @@ body
*
* Params:
* T = Array type.
* E = Array element type.
* size = Array size.
* allocator = Allocator.
*
* Returns: Newly created $(D_PSYMBOL RefCounted!T).
*
* Precondition: $(D_INLINECODE allocator !is null
* && size <= size_t.max / ElementType!T.sizeof)
* && size <= size_t.max / E.sizeof)
*/
RefCounted!T refCounted(T)(shared Allocator allocator, const size_t size)
RefCounted!T refCounted(T : E[], E)(shared Allocator allocator, size_t size)
@trusted
if (isArray!T)
in
{
assert(allocator !is null);
assert(size <= size_t.max / ElementType!T.sizeof);
assert(size <= size_t.max / E.sizeof);
}
body
do
{
return RefCounted!T(allocator.make!T(size), allocator);
}
///
unittest
@nogc @system unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(rc.count == 1);
@ -575,59 +409,6 @@ unittest
assert(rc.count == 1);
}
private @nogc unittest
{
struct E
{
}
auto b = defaultAllocator.refCounted!B(15);
static assert(is(typeof(b.storage.payload) == B*));
static assert(is(typeof(b.prop) == int));
static assert(!is(typeof(defaultAllocator.refCounted!B())));
static assert(is(typeof(defaultAllocator.refCounted!E())));
static assert(!is(typeof(defaultAllocator.refCounted!E(5))));
{
auto rc = defaultAllocator.refCounted!B(3);
assert(rc.get().prop == 3);
}
{
auto rc = defaultAllocator.refCounted!E();
assert(rc.count);
}
}
private @nogc unittest
{
auto rc = defaultAllocator.refCounted!(int[])(5);
assert(rc.length == 5);
}
private @nogc unittest
{
auto p1 = defaultAllocator.make!int(5);
auto p2 = p1;
auto rc = RefCounted!int(p1, defaultAllocator);
assert(rc.get() is p2);
}
private @nogc unittest
{
static bool destroyed = false;
struct F
{
~this() @nogc
{
destroyed = true;
}
}
{
auto rc = defaultAllocator.refCounted!F();
}
assert(destroyed);
}
/**
* $(D_PSYMBOL Unique) stores an object that gets destroyed at the end of its scope.
*
@ -660,13 +441,13 @@ struct Unique(T)
this.payload = value;
}
/// Ditto.
/// ditto
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
do
{
this.allocator_ = allocator;
}
@ -706,14 +487,14 @@ struct Unique(T)
return this;
}
/// Ditto.
/// ditto
ref typeof(this) opAssign(typeof(null))
{
allocator.dispose(this.payload);
return this;
}
/// Ditto.
/// ditto
ref typeof(this) opAssign(typeof(this) rhs)
{
swap(this.allocator_, rhs.allocator_);
@ -723,7 +504,7 @@ struct Unique(T)
}
///
@nogc unittest
@nogc nothrow pure @system unittest
{
auto rc = defaultAllocator.unique!int(5);
rc = defaultAllocator.make!int(7);
@ -770,7 +551,7 @@ struct Unique(T)
}
///
@nogc unittest
@nogc nothrow pure @system unittest
{
Unique!int u;
assert(!u.isInitialized);
@ -789,7 +570,7 @@ struct Unique(T)
}
///
@nogc unittest
@nogc nothrow pure @system unittest
{
auto u = defaultAllocator.unique!int(5);
assert(u.isInitialized);
@ -804,7 +585,7 @@ struct Unique(T)
}
///
@nogc unittest
@nogc nothrow pure @system unittest
{
auto p = defaultAllocator.make!int(5);
auto s = Unique!int(p, defaultAllocator);
@ -812,13 +593,13 @@ struct Unique(T)
}
///
@nogc unittest
@nogc nothrow @system unittest
{
static bool destroyed = false;
static bool destroyed;
struct F
static struct F
{
~this() @nogc
~this() @nogc nothrow @safe
{
destroyed = true;
}
@ -851,7 +632,7 @@ in
{
assert(allocator !is null);
}
body
do
{
auto payload = allocator.make!(T, A)(args);
return Unique!T(payload, allocator);
@ -863,63 +644,24 @@ body
*
* Params:
* T = Array type.
* E = Array element type.
* size = Array size.
* allocator = Allocator.
*
* Returns: Newly created $(D_PSYMBOL Unique!T).
*
* Precondition: $(D_INLINECODE allocator !is null
* && size <= size_t.max / ElementType!T.sizeof)
* && size <= size_t.max / E.sizeof)
*/
Unique!T unique(T)(shared Allocator allocator, const size_t size)
Unique!T unique(T : E[], E)(shared Allocator allocator, size_t size)
@trusted
if (isArray!T)
in
{
assert(allocator !is null);
assert(size <= size_t.max / ElementType!T.sizeof);
assert(size <= size_t.max / E.sizeof);
}
body
do
{
auto payload = allocator.resize!(ElementType!T)(null, size);
auto payload = allocator.resize!E(null, size);
return Unique!T(payload, allocator);
}
private unittest
{
static assert(is(typeof(defaultAllocator.unique!B(5))));
static assert(is(typeof(defaultAllocator.unique!(int[])(5))));
}
private unittest
{
auto s = defaultAllocator.unique!int(5);
assert(*s == 5);
s = null;
assert(s is null);
}
private unittest
{
auto s = defaultAllocator.unique!int(5);
assert(*s == 5);
s = defaultAllocator.unique!int(4);
assert(*s == 4);
}
private @nogc unittest
{
auto p1 = defaultAllocator.make!int(5);
auto p2 = p1;
auto rc = Unique!int(p1, defaultAllocator);
assert(rc.get() is p2);
}
private @nogc unittest
{
auto rc = Unique!int(defaultAllocator);
assert(rc.allocator is defaultAllocator);
}

17
os/dub.json Normal file
View File

@ -0,0 +1,17 @@
{
"name": "os",
"description": "Platform-independent interfaces to operating system functionality",
"targetType": "library",
"dependencies": {
"tanya:meta": "*"
},
"sourcePaths": [
"."
],
"importPaths": [
"."
],
"dflags-dmd": ["-dip1000"]
}

View File

@ -5,15 +5,17 @@
/**
* This module provides a portable way of using operating system error codes.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/os/error.d,
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/os/tanya/os/error.d,
* tanya/os/error.d)
*/
module tanya.os.error;
import tanya.meta.trait;
// Socket API error.
private template SAError(int posix, int wsa = posix)
{
@ -120,7 +122,7 @@ struct ErrorCode
/// Protocol not available.
noProtocolOption = SAError!(92, 42),
/// The protocol is not implemented orR has not been configured.
/// The protocol is not implemented or has not been configured.
protocolNotSupported = SAError!(93, 43),
/// The support for the specified socket type does not exist in this
@ -186,19 +188,66 @@ struct ErrorCode
cancelled = SAError!(125, 103),
}
/**
* Error descriptions.
*/
private enum ErrorStr : string
{
success = "The operation completed successfully",
noPermission = "Operation not permitted",
interrupted = "Interrupted system call",
badDescriptor = "Bad file descriptor",
wouldBlock = "An operation on a non-blocking socket would block",
noMemory = "Out of memory",
accessDenied = "Access denied",
fault = "An invalid pointer address detected",
noSuchDevice = "No such device",
invalidArgument = "An invalid argument was supplied",
tooManyDescriptors = "The limit on the number of open file descriptors",
noDescriptors = "The limit on the number of open file descriptors",
brokenPipe = "Broken pipe",
nameTooLong = "The name was too long",
notSocket = "A socket operation was attempted on a non-socket",
protocolError = "Protocol error",
messageTooLong = "Message too long",
wrongProtocolType = "Wrong protocol type for socket",
noProtocolOption = "Protocol not available",
protocolNotSupported = "The protocol is not implemented or has not been configured",
socketNotSupported = "Socket type not supported",
operationNotSupported = "The address family is no supported by the protocol family",
addressFamilyNotSupported = "Address family specified is not supported",
addressInUse = "Address already in use",
networkDown = "The network is not available",
networkUnreachable = "No route to host",
networkReset = "Network dropped connection because of reset",
connectionAborted = "The connection has been aborted",
connectionReset = "Connection reset by peer",
noBufferSpace = "No free buffer space is available for a socket operation",
alreadyConnected = "Transport endpoint is already connected",
notConnected = "Transport endpoint is not connected",
shutdown = "Cannot send after transport endpoint shutdown",
timedOut = "Operation timed out",
connectionRefused = "Connection refused",
hostDown = "Host is down",
hostUnreachable = "No route to host",
alreadyStarted = "Operation already in progress",
inProgress = "Operation now in progress",
cancelled = "Operation cancelled",
}
/**
* Constructor.
*
* Params:
* value = Numeric error code.
*/
this(const ErrorNo value) pure nothrow @safe @nogc
this(const ErrorNo value) @nogc nothrow pure @safe
{
this.value_ = value;
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
ErrorCode ec;
assert(ec == ErrorCode.success);
@ -211,13 +260,13 @@ struct ErrorCode
* Resets this $(D_PSYMBOL ErrorCode) to default
* ($(D_PSYMBOL ErrorCode.success)).
*/
void reset() pure nothrow @safe @nogc
void reset() @nogc nothrow pure @safe
{
this.value_ = ErrorNo.success;
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
auto ec = ErrorCode(ErrorCode.fault);
assert(ec == ErrorCode.fault);
@ -234,14 +283,14 @@ struct ErrorCode
return this.value_;
}
/// Ditto.
/// ditto
ErrorNo opCast(T : int)() const
{
return this.value_;
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
ErrorCode ec = ErrorCode.fault;
auto errorNo = cast(ErrorCode.ErrorNo) ec;
@ -258,23 +307,21 @@ struct ErrorCode
*
* Returns: $(D_KEYWORD this).
*/
ref ErrorCode opAssign(const ErrorNo that) pure nothrow @safe @nogc
ref ErrorCode opAssign(const ErrorNo that) return @nogc nothrow pure @safe
{
this.value_ = that;
return this;
}
/// Ditto.
ref ErrorCode opAssign()(auto ref const ErrorCode that)
pure nothrow @safe @nogc
/// ditto
ref ErrorCode opAssign(const ErrorCode that) return @nogc nothrow pure @safe
{
this.value_ = that.value_;
return this;
}
///
pure nothrow @safe @nogc unittest
{
@nogc nothrow pure @safe unittest
{
ErrorCode ec;
assert(ec == ErrorCode.success);
@ -282,6 +329,9 @@ struct ErrorCode
ec = ErrorCode.fault;
assert(ec == ErrorCode.fault);
}
///
@nogc nothrow pure @safe unittest
{
auto ec1 = ErrorCode(ErrorCode.fault);
ErrorCode ec2;
@ -290,7 +340,6 @@ struct ErrorCode
ec2 = ec1;
assert(ec1 == ec2);
}
}
/**
* Equality with another error code or error code number.
@ -300,21 +349,19 @@ struct ErrorCode
*
* Returns: Whether $(D_KEYWORD this) and $(D_PARAM that) are equal.
*/
bool opEquals(const ErrorNo that) const pure nothrow @safe @nogc
bool opEquals(const ErrorNo that) const @nogc nothrow pure @safe
{
return this.value_ == that;
}
/// Ditto.
bool opEquals()(auto ref const ErrorCode that)
const pure nothrow @safe @nogc
/// ditto
bool opEquals(const ErrorCode that) const @nogc nothrow pure @safe
{
return this.value_ == that.value_;
}
///
pure nothrow @safe @nogc unittest
{
@nogc nothrow pure @safe unittest
{
ErrorCode ec1 = ErrorCode.fault;
ErrorCode ec2 = ErrorCode.accessDenied;
@ -323,6 +370,9 @@ struct ErrorCode
assert(ec1 != ErrorCode.accessDenied);
assert(ErrorCode.fault != ec2);
}
///
@nogc nothrow pure @safe unittest
{
ErrorCode ec1 = ErrorCode.fault;
ErrorCode ec2 = ErrorCode.fault;
@ -331,6 +381,30 @@ struct ErrorCode
assert(ec1 == ErrorCode.fault);
assert(ErrorCode.fault == ec2);
}
/**
* Returns string describing the error number. If a description for a
* specific error number is not available, returns $(D_KEYWORD null).
*
* Returns: String describing the error number.
*/
string toString() const @nogc nothrow pure @safe
{
foreach (e; __traits(allMembers, ErrorNo))
{
if (__traits(getMember, ErrorNo, e) == this.value_)
{
return __traits(getMember, ErrorStr, e);
}
}
return null;
}
///
@nogc nothrow pure @safe unittest
{
ErrorCode ec = ErrorCode.fault;
assert(ec.toString() == "An invalid pointer address detected");
}
private ErrorNo value_ = ErrorNo.success;

View File

@ -6,11 +6,11 @@
* This package provides platform-independent interfaces to operating system
* functionality.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/os/package.d,
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/os/tanya/os/package.d,
* tanya/os/package.d)
*/
module tanya.os;

View File

@ -0,0 +1,259 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Iteration algorithms.
*
* These algorithms wrap other ranges and modify the way, how the original
* range is iterated, or the order in which its elements are accessed.
*
* All algorithms in this module are lazy, they request the next element of the
* original range on demand.
*
* Copyright: Eugene Wissner 2018-2021.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/algorithm/iteration.d,
* tanya/algorithm/iteration.d)
*/
module tanya.algorithm.iteration;
import std.typecons;
import tanya.memory.lifetime;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range;
private struct SingletonByValue(E)
{
private Nullable!E element;
@disable this();
private this(U)(ref U element)
if (is(U == E))
{
this.element = move(element);
}
private this(U)(ref U element)
if (is(Unqual!U == Nullable!(Unqual!E)) || is(Unqual!U == Nullable!(const E)))
{
if (!element.isNull)
{
this.element = element.get;
}
}
@property ref inout(E) front() inout
in
{
assert(!empty);
}
do
{
return this.element.get;
}
alias back = front;
void popFront()
in
{
assert(!empty);
}
do
{
this.element.nullify();
}
alias popBack = popFront;
@property bool empty() const
{
return this.element.isNull;
}
@property size_t length() const
{
return !this.element.isNull;
}
auto save()
{
return SingletonByValue!E(this.element);
}
ref inout(E) opIndex(size_t i) inout
in
{
assert(!empty);
assert(i == 0);
}
do
{
return this.element.get;
}
}
private struct SingletonByRef(E)
{
private E* element;
@disable this();
private this(return ref E element) @trusted
{
this.element = &element;
}
@property ref inout(E) front() inout return
in
{
assert(!empty);
}
do
{
return *this.element;
}
alias back = front;
void popFront()
in
{
assert(!empty);
}
do
{
this.element = null;
}
alias popBack = popFront;
@property bool empty() const
{
return this.element is null;
}
@property size_t length() const
{
return this.element !is null;
}
auto save() return
{
return typeof(this)(*this.element);
}
ref inout(E) opIndex(size_t i) inout return
in
{
assert(!empty);
assert(i == 0);
}
do
{
return *this.element;
}
}
/**
* Creates a bidirectional and random-access range with the single element
* $(D_PARAM element).
*
* If $(D_PARAM element) is passed by value the resulting range stores it
* internally. If $(D_PARAM element) is passed by reference, the resulting
* range keeps only a pointer to the element.
*
* Params:
* E = Element type.
* element = Element.
*
* Returns: A range with one element.
*/
auto singleton(E)(return E element)
if (isMutable!E)
{
return SingletonByValue!E(element);
}
/// ditto
auto singleton(E)(return ref E element)
{
return SingletonByRef!E(element);
}
///
@nogc nothrow pure @safe unittest
{
auto singleChar = singleton('a');
assert(singleChar.length == 1);
assert(singleChar.front == 'a');
singleChar.popFront();
assert(singleChar.empty);
}
/**
* Accumulates all elements of a range using a function.
*
* $(D_PSYMBOL foldr) takes a function, a bidirectional range and the initial
* value. The function takes this initial value and the first element of the
* range (in this order), puts them together and returns the result. The return
* type of the function should be the same as the type of the initial value.
* This is than repeated for all the remaining elements of the range, whereby
* the value returned by the passed function is used at the place of the
* initial value.
*
* $(D_PSYMBOL foldr) accumulates from right to left.
*
* Params:
* F = Callable accepting the accumulator and a range element.
*/
template foldr(F...)
if (F.length == 1)
{
/**
* Params:
* R = Bidirectional range type.
* T = Type of the accumulated value.
* range = Bidirectional range.
* init = Initial value.
*
* Returns: Accumulated value.
*/
auto foldr(R, T)(scope R range, scope return auto ref T init)
if (isBidirectionalRange!R)
{
if (range.empty)
{
return init;
}
else
{
auto acc = F[0](init, getAndPopBack(range));
return foldr(range, acc);
}
}
}
///
@nogc nothrow pure @safe unittest
{
int[3] range = [1, 2, 3];
int[3] output;
const int[3] expected = [3, 2, 1];
alias f = (acc, x) {
acc.front = x;
acc.popFront;
return acc;
};
const actual = foldr!f(range[], output[]);
assert(output[] == expected[]);
}

View File

@ -0,0 +1,244 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Algorithms that modify its arguments.
*
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/algorithm/mutation.d,
* tanya/algorithm/mutation.d)
*/
module tanya.algorithm.mutation;
static import tanya.memory.lifetime;
static import tanya.memory.op;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range;
/**
* Copies the $(D_PARAM source) range into the $(D_PARAM target) range.
*
* Params:
* Source = Input range type.
* Target = Output range type.
* source = Source input range.
* target = Target output range.
*
* Returns: $(D_PARAM target) range, whose front element is the one past the
* last element copied.
*
* Precondition: $(D_PARAM target) should be large enough to accept all
* $(D_PARAM source) elements.
*/
Target copy(Source, Target)(Source source, Target target)
if (isInputRange!Source && isOutputRange!(Target, ElementType!Source))
in
{
static if (hasLength!Source && hasLength!Target)
{
assert(target.length >= source.length);
}
}
do
{
alias E = ElementType!Source;
static if (isDynamicArray!Source
&& is(Unqual!E == ElementType!Target)
&& !hasElaborateCopyConstructor!E
&& !hasElaborateAssign!E
&& !hasElaborateDestructor!E)
{
if (source.ptr < target.ptr
&& (() @trusted => (target.ptr - source.ptr) < source.length)())
{
tanya.memory.op.copyBackward(source, target);
}
else if (source.ptr !is target.ptr)
{
tanya.memory.op.copy(source, target);
}
return target[source.length .. $];
}
else
{
for (; !source.empty; source.popFront())
{
put(target, source.front);
}
return target;
}
}
///
@nogc nothrow pure @safe unittest
{
import std.algorithm.comparison : equal;
const int[2] source = [1, 2];
int[2] target = [3, 4];
copy(source[], target[]);
assert(equal(source[], target[]));
}
/**
* Fills $(D_PARAM range) with $(D_PARAM value).
*
* Params:
* Range = Input range type.
* Value = Filler type.
* range = Input range.
* value = Filler.
*/
void fill(Range, Value)(Range range, auto ref Value value)
if (isInputRange!Range && isAssignable!(ElementType!Range, Value))
{
static if (!isDynamicArray!Range && is(typeof(range[] = value)))
{
range[] = value;
}
else
{
for (; !range.empty; range.popFront())
{
range.front = value;
}
}
}
///
@nogc nothrow pure @safe unittest
{
import std.algorithm.comparison : equal;
int[6] actual;
const int[6] expected = [1, 1, 1, 1, 1, 1];
fill(actual[], 1);
assert(equal(actual[], expected[]));
}
/**
* Fills $(D_PARAM range) with $(D_PARAM value) assuming the elements of the
* $(D_PARAM range) aren't initialized.
*
* Params:
* Range = Input range type.
* Value = Initializer type.
* range = Input range.
* value = Initializer.
*/
void uninitializedFill(Range, Value)(Range range, auto ref Value value)
if (isInputRange!Range && hasLvalueElements!Range
&& isAssignable!(ElementType!Range, Value))
{
static if (hasElaborateDestructor!(ElementType!Range))
{
for (; !range.empty; range.popFront())
{
ElementType!Range* p = &range.front;
tanya.memory.lifetime.emplace!(ElementType!Range)(cast(void[]) (p[0 .. 1]), value);
}
}
else
{
fill(range, value);
}
}
///
@nogc nothrow pure @safe unittest
{
import std.algorithm.comparison : equal;
int[6] actual = void;
const int[6] expected = [1, 1, 1, 1, 1, 1];
uninitializedFill(actual[], 1);
assert(equal(actual[], expected[]));
}
/**
* Initializes all elements of the $(D_PARAM range) assuming that they are
* uninitialized.
*
* Params:
* Range = Input range type
* range = Input range.
*/
void initializeAll(Range)(Range range) @trusted
if (isInputRange!Range && hasLvalueElements!Range)
{
import tanya.memory.op : copy, fill;
alias T = ElementType!Range;
static if (isDynamicArray!Range && __traits(isZeroInit, T))
{
fill!0(range);
}
else
{
static immutable init = T.init;
for (; !range.empty; range.popFront())
{
copy((&init)[0 .. 1], (&range.front)[0 .. 1]);
}
}
}
///
@nogc nothrow pure @safe unittest
{
import std.algorithm.comparison : equal;
int[2] actual = void;
const int[2] expected = [0, 0];
initializeAll(actual[]);
assert(equal(actual[], expected[]));
}
/**
* Destroys all elements in the $(D_PARAM range).
*
* This function has effect only if the element type of $(D_PARAM Range) has
* an elaborate destructor, i.e. it is a $(D_PSYMBOL struct) with an explicit
* or generated by the compiler destructor.
*
* Params:
* Range = Input range type.
* range = Input range.
*/
void destroyAll(Range)(Range range)
if (isInputRange!Range && hasLvalueElements!Range)
{
tanya.memory.lifetime.destroyAllImpl!(Range, ElementType!Range)(range);
}
///
@nogc nothrow pure @trusted unittest
{
static struct WithDtor
{
private size_t* counter;
~this() @nogc nothrow pure
{
if (this.counter !is null)
{
++(*this.counter);
}
}
}
size_t counter;
WithDtor[2] withDtor = [WithDtor(&counter), WithDtor(&counter)];
destroyAll(withDtor[]);
assert(counter == 2);
}

View File

@ -0,0 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Collection of generic algorithms.
*
* Copyright: Eugene Wissner 2017-2021.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/algorithm/package.d,
* tanya/algorithm/package.d)
*/
module tanya.algorithm;
public import tanya.algorithm.iteration;
public import tanya.algorithm.mutation;

View File

@ -1,188 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Event loop implementation for Linux.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/event/epoll.d,
* tanya/async/event/epoll.d)
*/
module tanya.async.event.epoll;
version (D_Ddoc)
{
}
else version (linux):
public import core.sys.linux.epoll;
import tanya.async.protocol;
import tanya.async.event.selector;
import tanya.async.loop;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.array;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
import core.stdc.errno;
import core.sys.posix.unistd;
import core.time;
import std.algorithm.comparison;
extern (C) nothrow @nogc
{
int epoll_create1(int flags);
int epoll_ctl (int epfd, int op, int fd, epoll_event *event);
int epoll_wait (int epfd, epoll_event *events, int maxevents, int timeout);
}
final class EpollLoop : SelectorLoop
{
protected int fd;
private Array!epoll_event events;
/**
* Initializes the loop.
*/
this() @nogc
{
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
{
throw defaultAllocator.make!BadLoopException("epoll initialization failed");
}
super();
events = Array!epoll_event(maxEvents, MmapPool.instance);
}
/**
* Frees loop internals.
*/
~this() @nogc
{
close(fd);
}
/**
* Should be called if the backend configuration changes.
*
* Params:
* watcher = Watcher.
* oldEvents = The events were already set.
* events = The events should be set.
*
* Returns: $(D_KEYWORD true) if the operation was successful.
*/
protected override bool reify(SocketWatcher watcher,
EventMask oldEvents,
EventMask events) @nogc
{
int op = EPOLL_CTL_DEL;
epoll_event ev;
if (events == oldEvents)
{
return true;
}
if (events && oldEvents)
{
op = EPOLL_CTL_MOD;
}
else if (events && !oldEvents)
{
op = EPOLL_CTL_ADD;
}
ev.data.fd = watcher.socket.handle;
ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0)
| (events & Event.write ? EPOLLOUT : 0)
| EPOLLET;
return epoll_ctl(fd, op, watcher.socket.handle, &ev) == 0;
}
/**
* Does the actual polling.
*/
protected override void poll() @nogc
{
// Don't block
immutable timeout = cast(immutable int) blockTime.total!"msecs";
auto eventCount = epoll_wait(fd, events.get().ptr, maxEvents, timeout);
if (eventCount < 0)
{
if (errno != EINTR)
{
throw defaultAllocator.make!BadLoopException();
}
return;
}
for (auto i = 0; i < eventCount; ++i)
{
auto transport = cast(StreamTransport) connections[events[i].data.fd];
if (transport is null)
{
auto connection = cast(ConnectionWatcher) connections[events[i].data.fd];
assert(connection !is null);
acceptConnections(connection);
}
else if (events[i].events & EPOLLERR)
{
kill(transport);
continue;
}
else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP))
{
SocketException exception;
try
{
ptrdiff_t received;
do
{
received = transport.socket.receive(transport.output[]);
transport.output += received;
}
while (received);
}
catch (SocketException e)
{
exception = e;
}
if (transport.socket.disconnected)
{
kill(transport, exception);
continue;
}
else if (transport.output.length)
{
pendings.enqueue(transport);
}
}
if (events[i].events & EPOLLOUT)
{
transport.writeReady = true;
if (transport.input.length)
{
feed(transport);
}
}
}
}
/**
* Returns: The blocking time.
*/
override protected @property inout(Duration) blockTime()
inout @safe pure nothrow
{
return min(super.blockTime, 1.dur!"seconds");
}
}

View File

@ -1,391 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Event loop implementation for Windows.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/event/iocp.d,
* tanya/async/event/iocp.d)
*/
module tanya.async.event.iocp;
version (D_Ddoc)
{
}
else version (Windows):
import tanya.container.buffer;
import tanya.async.loop;
import tanya.async.protocol;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
import core.sys.windows.basetyps;
import core.sys.windows.mswsock;
import core.sys.windows.winbase;
import core.sys.windows.windef;
import core.sys.windows.winsock2;
/**
* Transport for stream sockets.
*/
final class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
{
private SocketException exception;
private ReadBuffer!ubyte output;
private WriteBuffer!ubyte input;
private Protocol protocol_;
private bool closing;
/**
* Creates new completion port transport.
*
* Params:
* socket = Socket.
*
* Precondition: $(D_INLINECODE socket !is null)
*/
this(OverlappedConnectedSocket socket) @nogc
{
super(socket);
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
input = WriteBuffer!ubyte(8192, MmapPool.instance);
active = true;
}
/**
* Returns: Socket.
*
* Postcondition: $(D_INLINECODE socket !is null)
*/
override @property OverlappedConnectedSocket socket() pure nothrow @safe @nogc
out (socket)
{
assert(socket !is null);
}
body
{
return cast(OverlappedConnectedSocket) socket_;
}
/**
* Returns $(D_PARAM true) if the transport is closing or closed.
*/
bool isClosing() const pure nothrow @safe @nogc
{
return closing;
}
/**
* Close the transport.
*
* Buffered data will be flushed. No more data will be received.
*/
void close() pure nothrow @safe @nogc
{
closing = true;
}
/**
* Write some data to the transport.
*
* Params:
* data = Data to send.
*/
void write(ubyte[] data) @nogc
{
input ~= data;
}
/**
* Returns: Application protocol.
*/
@property Protocol protocol() pure nothrow @safe @nogc
{
return protocol_;
}
/**
* Switches the protocol.
*
* The protocol is deallocated by the event loop, it should currently be
* allocated with $(D_PSYMBOL MmapPool).
*
* Params:
* protocol = Application protocol.
*
* Precondition: $(D_INLINECODE protocol !is null)
*/
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
in
{
assert(protocol !is null);
}
body
{
protocol_ = protocol;
}
/**
* Invokes the watcher callback.
*/
override void invoke() @nogc
{
if (output.length)
{
immutable empty = input.length == 0;
protocol.received(output[0 .. $]);
output.clear();
if (empty)
{
SocketState overlapped;
try
{
overlapped = MmapPool.instance.make!SocketState;
socket.beginSend(input[], overlapped);
}
catch (SocketException e)
{
MmapPool.instance.dispose(overlapped);
MmapPool.instance.dispose(e);
}
}
}
else
{
protocol.disconnected(exception);
MmapPool.instance.dispose(protocol_);
defaultAllocator.dispose(exception);
active = false;
}
}
}
final class IOCPLoop : Loop
{
protected HANDLE completionPort;
protected OVERLAPPED overlap;
/**
* Initializes the loop.
*/
this() @nogc
{
super();
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (!completionPort)
{
throw make!BadLoopException(defaultAllocator,
"Creating completion port failed");
}
}
/**
* Should be called if the backend configuration changes.
*
* Params:
* watcher = Watcher.
* oldEvents = The events were already set.
* events = The events should be set.
*
* Returns: $(D_KEYWORD true) if the operation was successful.
*/
override protected bool reify(SocketWatcher watcher,
EventMask oldEvents,
EventMask events) @nogc
{
SocketState overlapped;
if (!(oldEvents & Event.accept) && (events & Event.accept))
{
auto socket = cast(OverlappedStreamSocket) watcher.socket;
assert(socket !is null);
if (CreateIoCompletionPort(cast(HANDLE) socket.handle,
completionPort,
cast(ULONG_PTR) (cast(void*) watcher),
0) !is completionPort)
{
return false;
}
try
{
overlapped = MmapPool.instance.make!SocketState;
socket.beginAccept(overlapped);
}
catch (SocketException e)
{
MmapPool.instance.dispose(overlapped);
defaultAllocator.dispose(e);
return false;
}
}
if ((!(oldEvents & Event.read) && (events & Event.read))
|| (!(oldEvents & Event.write) && (events & Event.write)))
{
auto transport = cast(StreamTransport) watcher;
assert(transport !is null);
if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle,
completionPort,
cast(ULONG_PTR) (cast(void*) watcher),
0) !is completionPort)
{
return false;
}
// Begin to read
if (!(oldEvents & Event.read) && (events & Event.read))
{
try
{
overlapped = MmapPool.instance.make!SocketState;
transport.socket.beginReceive(transport.output[], overlapped);
}
catch (SocketException e)
{
MmapPool.instance.dispose(overlapped);
defaultAllocator.dispose(e);
return false;
}
}
}
return true;
}
private void kill(StreamTransport transport,
SocketException exception = null) @nogc
in
{
assert(transport !is null);
}
body
{
transport.socket.shutdown();
defaultAllocator.dispose(transport.socket);
transport.exception = exception;
pendings.enqueue(transport);
}
/**
* Does the actual polling.
*/
override protected void poll() @nogc
{
DWORD lpNumberOfBytes;
ULONG_PTR key;
LPOVERLAPPED overlap;
immutable timeout = cast(immutable int) blockTime.total!"msecs";
auto result = GetQueuedCompletionStatus(completionPort,
&lpNumberOfBytes,
&key,
&overlap,
timeout);
if (result == FALSE && overlap == NULL)
{
return; // Timeout
}
auto overlapped = (cast(SocketState) ((cast(void*) overlap) - 8));
assert(overlapped !is null);
scope (failure)
{
MmapPool.instance.dispose(overlapped);
}
switch (overlapped.event)
{
case OverlappedSocketEvent.accept:
auto connection = cast(ConnectionWatcher) (cast(void*) key);
assert(connection !is null);
auto listener = cast(OverlappedStreamSocket) connection.socket;
assert(listener !is null);
auto socket = listener.endAccept(overlapped);
auto transport = MmapPool.instance.make!StreamTransport(socket);
connection.incoming.enqueue(transport);
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
pendings.enqueue(connection);
listener.beginAccept(overlapped);
break;
case OverlappedSocketEvent.read:
auto transport = cast(StreamTransport) (cast(void*) key);
assert(transport !is null);
if (!transport.active)
{
MmapPool.instance.dispose(transport);
MmapPool.instance.dispose(overlapped);
return;
}
int received;
SocketException exception;
try
{
received = transport.socket.endReceive(overlapped);
}
catch (SocketException e)
{
exception = e;
}
if (transport.socket.disconnected)
{
// We want to get one last notification to destroy the watcher.
transport.socket.beginReceive(transport.output[], overlapped);
kill(transport, exception);
}
else if (received > 0)
{
immutable full = transport.output.free == received;
transport.output += received;
// Receive was interrupted because the buffer is full. We have to continue.
if (full)
{
transport.socket.beginReceive(transport.output[], overlapped);
}
pendings.enqueue(transport);
}
break;
case OverlappedSocketEvent.write:
auto transport = cast(StreamTransport) (cast(void*) key);
assert(transport !is null);
transport.input += transport.socket.endSend(overlapped);
if (transport.input.length > 0)
{
transport.socket.beginSend(transport.input[], overlapped);
}
else
{
transport.socket.beginReceive(transport.output[], overlapped);
if (transport.isClosing())
{
kill(transport);
}
}
break;
default:
assert(false, "Unknown event");
}
}
}

View File

@ -1,332 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Event loop implementation for *BSD.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/event/kqueue.d,
* tanya/async/event/kqueue.d)
*/
module tanya.async.event.kqueue;
version (D_Ddoc)
{
}
else version (OSX)
{
version = MacBSD;
}
else version (iOS)
{
version = MacBSD;
}
else version (TVOS)
{
version = MacBSD;
}
else version (WatchOS)
{
version = MacBSD;
}
else version (FreeBSD)
{
version = MacBSD;
}
else version (OpenBSD)
{
version = MacBSD;
}
else version (DragonFlyBSD)
{
version = MacBSD;
}
version (MacBSD):
import core.stdc.errno;
import core.sys.posix.time; // timespec
import core.sys.posix.unistd;
import core.time;
import std.algorithm.comparison;
import tanya.async.event.selector;
import tanya.async.loop;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.array;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc
{
*kevp = kevent_t(args);
}
enum : short
{
EVFILT_READ = -1,
EVFILT_WRITE = -2,
EVFILT_AIO = -3, /* attached to aio requests */
EVFILT_VNODE = -4, /* attached to vnodes */
EVFILT_PROC = -5, /* attached to struct proc */
EVFILT_SIGNAL = -6, /* attached to struct proc */
EVFILT_TIMER = -7, /* timers */
EVFILT_MACHPORT = -8, /* Mach portsets */
EVFILT_FS = -9, /* filesystem events */
EVFILT_USER = -10, /* User events */
EVFILT_VM = -12, /* virtual memory events */
EVFILT_SYSCOUNT = 11
}
struct kevent_t
{
uintptr_t ident; /* identifier for this event */
short filter; /* filter for event */
ushort flags;
uint fflags;
intptr_t data;
void *udata; /* opaque user data identifier */
}
enum
{
/* actions */
EV_ADD = 0x0001, /* add event to kq (implies enable) */
EV_DELETE = 0x0002, /* delete event from kq */
EV_ENABLE = 0x0004, /* enable event */
EV_DISABLE = 0x0008, /* disable event (not reported) */
/* flags */
EV_ONESHOT = 0x0010, /* only report one occurrence */
EV_CLEAR = 0x0020, /* clear event state after reporting */
EV_RECEIPT = 0x0040, /* force EV_ERROR on success, data=0 */
EV_DISPATCH = 0x0080, /* disable event after reporting */
EV_SYSFLAGS = 0xF000, /* reserved by system */
EV_FLAG1 = 0x2000, /* filter-specific flag */
/* returned values */
EV_EOF = 0x8000, /* EOF detected */
EV_ERROR = 0x4000, /* error, data contains errno */
}
extern(C) int kqueue() nothrow @nogc;
extern(C) int kevent(int kq, const kevent_t *changelist, int nchanges,
kevent_t *eventlist, int nevents, const timespec *timeout)
nothrow @nogc;
final class KqueueLoop : SelectorLoop
{
protected int fd;
private Array!kevent_t events;
private Array!kevent_t changes;
private size_t changeCount;
/**
* Returns: Maximal event count can be got at a time
* (should be supported by the backend).
*/
override protected @property uint maxEvents()
const pure nothrow @safe @nogc
{
return cast(uint) events.length;
}
this() @nogc
{
super();
if ((fd = kqueue()) == -1)
{
throw make!BadLoopException(defaultAllocator,
"kqueue initialization failed");
}
events = Array!kevent_t(64, MmapPool.instance);
changes = Array!kevent_t(64, MmapPool.instance);
}
/**
* Frees loop internals.
*/
~this() @nogc
{
close(fd);
}
private void set(SocketType socket, short filter, ushort flags) @nogc
{
if (changes.length <= changeCount)
{
changes.length = changeCount + maxEvents;
}
EV_SET(&changes[changeCount],
cast(ulong) socket,
filter,
flags,
0U,
0L,
null);
++changeCount;
}
/**
* Should be called if the backend configuration changes.
*
* Params:
* watcher = Watcher.
* oldEvents = The events were already set.
* events = The events should be set.
*
* Returns: $(D_KEYWORD true) if the operation was successful.
*/
override protected bool reify(SocketWatcher watcher,
EventMask oldEvents,
EventMask events) @nogc
{
if (events != oldEvents)
{
if (oldEvents & Event.read || oldEvents & Event.accept)
{
set(watcher.socket.handle, EVFILT_READ, EV_DELETE);
}
if (oldEvents & Event.write)
{
set(watcher.socket.handle, EVFILT_WRITE, EV_DELETE);
}
}
if (events & (Event.read | events & Event.accept))
{
set(watcher.socket.handle, EVFILT_READ, EV_ADD | EV_ENABLE);
}
if (events & Event.write)
{
set(watcher.socket.handle, EVFILT_WRITE, EV_ADD | EV_DISPATCH);
}
return true;
}
/**
* Does the actual polling.
*/
protected override void poll() @nogc
{
timespec ts;
blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec);
if (changeCount > maxEvents)
{
events.length = changes.length;
}
auto eventCount = kevent(fd,
changes.get().ptr,
cast(int) changeCount,
events.get().ptr,
maxEvents,
&ts);
changeCount = 0;
if (eventCount < 0)
{
if (errno != EINTR)
{
throw defaultAllocator.make!BadLoopException();
}
return;
}
for (int i; i < eventCount; ++i)
{
assert(connections.length > events[i].ident);
auto transport = cast(StreamTransport) connections[events[i].ident];
// If it is a ConnectionWatcher. Accept connections.
if (transport is null)
{
auto connection = cast(ConnectionWatcher) connections[events[i].ident];
assert(connection !is null);
acceptConnections(connection);
}
else if (events[i].flags & EV_ERROR)
{
kill(transport);
}
else if (events[i].filter == EVFILT_READ)
{
SocketException exception;
try
{
ptrdiff_t received;
do
{
received = transport.socket.receive(transport.output[]);
transport.output += received;
}
while (received);
}
catch (SocketException e)
{
exception = e;
}
if (transport.socket.disconnected)
{
kill(transport, exception);
}
else if (transport.output.length)
{
pendings.enqueue(transport);
}
}
else if (events[i].filter == EVFILT_WRITE)
{
transport.writeReady = true;
if (transport.input.length)
{
feed(transport);
}
}
}
}
/**
* Returns: The blocking time.
*/
override protected @property inout(Duration) blockTime()
inout @nogc @safe pure nothrow
{
return min(super.blockTime, 1.dur!"seconds");
}
/**
* If the transport couldn't send the data, the further sending should
* be handled by the event loop.
*
* Params:
* transport = Transport.
* exception = Exception thrown on sending.
*
* Returns: $(D_KEYWORD true) if the operation could be successfully
* completed or scheduled, $(D_KEYWORD false) otherwise (the
* transport will be destroyed then).
*/
protected override bool feed(StreamTransport transport,
SocketException exception = null) @nogc
{
if (!super.feed(transport, exception))
{
return false;
}
if (!transport.writeReady)
{
set(transport.socket.handle, EVFILT_WRITE, EV_DISPATCH);
return true;
}
return false;
}
}

View File

@ -1,407 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* This module contains base implementations for reactor event loops.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/event/selector.d,
* tanya/async/event/selector.d)
*/
module tanya.async.event.selector;
version (D_Ddoc)
{
}
else version (Posix):
import tanya.async.loop;
import tanya.async.protocol;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.buffer;
import tanya.container.array;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
/**
* Transport for stream sockets.
*/
package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
{
private SelectorLoop loop;
private SocketException exception;
package ReadBuffer!ubyte output;
package WriteBuffer!ubyte input;
private Protocol protocol_;
private bool closing;
/// Received notification that the underlying socket is write-ready.
package bool writeReady;
/**
* Params:
* loop = Event loop.
* socket = Socket.
*
* Precondition: $(D_INLINECODE loop !is null && socket !is null)
*/
this(SelectorLoop loop, ConnectedSocket socket) @nogc
in
{
assert(loop !is null);
}
body
{
super(socket);
this.loop = loop;
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
input = WriteBuffer!ubyte(8192, MmapPool.instance);
active = true;
}
/**
* Returns: Socket.
*
* Postcondition: $(D_INLINECODE socket !is null)
*/
override @property ConnectedSocket socket() pure nothrow @safe @nogc
out (socket)
{
assert(socket !is null);
}
body
{
return cast(ConnectedSocket) socket_;
}
private @property void socket(ConnectedSocket socket)
pure nothrow @safe @nogc
in
{
assert(socket !is null);
}
body
{
socket_ = socket;
}
/**
* Returns: Application protocol.
*/
@property Protocol protocol() pure nothrow @safe @nogc
{
return protocol_;
}
/**
* Switches the protocol.
*
* The protocol is deallocated by the event loop, it should currently be
* allocated with $(D_PSYMBOL MmapPool).
*
* Params:
* protocol = Application protocol.
*
* Precondition: $(D_INLINECODE protocol !is null)
*/
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
in
{
assert(protocol !is null);
}
body
{
protocol_ = protocol;
}
/**
* Returns $(D_PARAM true) if the transport is closing or closed.
*/
bool isClosing() const pure nothrow @safe @nogc
{
return closing;
}
/**
* Close the transport.
*
* Buffered data will be flushed. No more data will be received.
*/
void close() @nogc
{
closing = true;
loop.reify(this,
EventMask(Event.read, Event.write),
EventMask(Event.write));
}
/**
* Invokes the watcher callback.
*/
override void invoke() @nogc
{
if (output.length)
{
protocol.received(output[0 .. $]);
output.clear();
if (isClosing() && input.length == 0)
{
loop.kill(this);
}
}
else
{
protocol.disconnected(exception);
MmapPool.instance.dispose(protocol_);
defaultAllocator.dispose(exception);
active = false;
}
}
/**
* Write some data to the transport.
*
* Params:
* data = Data to send.
*/
void write(ubyte[] data) @nogc
{
if (!data.length)
{
return;
}
// Try to write if the socket is write ready.
if (writeReady)
{
ptrdiff_t sent;
SocketException exception;
try
{
sent = socket.send(data);
if (sent == 0)
{
writeReady = false;
}
}
catch (SocketException e)
{
writeReady = false;
exception = e;
}
if (sent < data.length)
{
input ~= data[sent..$];
loop.feed(this, exception);
}
}
else
{
input ~= data;
}
}
}
abstract class SelectorLoop : Loop
{
/// Pending connections.
protected Array!SocketWatcher connections;
this() @nogc
{
super();
connections = Array!SocketWatcher(maxEvents, MmapPool.instance);
}
~this() @nogc
{
foreach (ref connection; connections)
{
// We want to free only the transports. ConnectionWatcher are
// created by the user and should be freed by himself.
if (cast(StreamTransport) connection !is null)
{
MmapPool.instance.dispose(connection);
}
}
}
/**
* Should be called if the backend configuration changes.
*
* Params:
* watcher = Watcher.
* oldEvents = The events were already set.
* events = The events should be set.
*
* Returns: $(D_KEYWORD true) if the operation was successful.
*/
override abstract protected bool reify(SocketWatcher watcher,
EventMask oldEvents,
EventMask events) @nogc;
/**
* Kills the watcher and closes the connection.
*
* Params:
* transport = Transport.
* exception = Occurred exception.
*/
protected void kill(StreamTransport transport,
SocketException exception = null) @nogc
in
{
assert(transport !is null);
}
body
{
transport.socket.shutdown();
defaultAllocator.dispose(transport.socket);
transport.exception = exception;
pendings.enqueue(transport);
}
/**
* If the transport couldn't send the data, the further sending should
* be handled by the event loop.
*
* Params:
* transport = Transport.
* exception = Exception thrown on sending.
*
* Returns: $(D_KEYWORD true) if the operation could be successfully
* completed or scheduled, $(D_KEYWORD false) otherwise (the
* transport will be destroyed then).
*/
protected bool feed(StreamTransport transport,
SocketException exception = null) @nogc
in
{
assert(transport !is null);
}
body
{
while (transport.input.length && transport.writeReady)
{
try
{
ptrdiff_t sent = transport.socket.send(transport.input[]);
if (sent == 0)
{
transport.writeReady = false;
}
else
{
transport.input += sent;
}
}
catch (SocketException e)
{
exception = e;
transport.writeReady = false;
}
}
if (exception !is null)
{
kill(transport, exception);
return false;
}
if (transport.input.length == 0 && transport.isClosing())
{
kill(transport);
}
return true;
}
/**
* Start watching.
*
* Params:
* watcher = Watcher.
*/
override void start(ConnectionWatcher watcher) @nogc
{
if (watcher.active)
{
return;
}
if (connections.length <= watcher.socket)
{
connections.length = watcher.socket.handle + maxEvents / 2;
}
connections[watcher.socket.handle] = watcher;
super.start(watcher);
}
/**
* Accept incoming connections.
*
* Params:
* connection = Connection watcher ready to accept.
*/
package void acceptConnections(ConnectionWatcher connection) @nogc
in
{
assert(connection !is null);
}
body
{
while (true)
{
ConnectedSocket client;
try
{
client = (cast(StreamSocket) connection.socket).accept();
}
catch (SocketException e)
{
defaultAllocator.dispose(e);
break;
}
if (client is null)
{
break;
}
StreamTransport transport;
if (connections.length > client.handle)
{
transport = cast(StreamTransport) connections[client.handle];
}
else
{
connections.length = client.handle + maxEvents / 2;
}
if (transport is null)
{
transport = MmapPool.instance.make!StreamTransport(this, client);
connections[client.handle] = transport;
}
else
{
transport.socket = client;
}
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
connection.incoming.enqueue(transport);
}
if (!connection.incoming.empty)
{
pendings.enqueue(connection);
}
}
}

View File

@ -1,57 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This module provides API for Windows I/O Completion Ports.
*
* Note: Available only on Windows.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/iocp.d,
* tanya/async/iocp.d)
*/
module tanya.async.iocp;
version (Windows)
{
version = WindowsDoc;
}
else version (D_Ddoc)
{
version = WindowsDoc;
version (Windows)
{
}
else
{
private struct OVERLAPPED
{
}
private alias HANDLE = void*;
}
}
version (WindowsDoc):
import core.sys.windows.winbase;
import core.sys.windows.windef;
/**
* Provides an extendable representation of a Win32 $(D_PSYMBOL OVERLAPPED)
* structure.
*/
class State
{
/// For internal use by Windows API.
align(1) OVERLAPPED overlapped;
/// File/socket handle.
HANDLE handle;
/// For keeping events or event masks.
int event;
}

View File

@ -1,435 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Interface for the event loop implementations and the default event loop
* chooser.
*
* ---
* import tanya.async;
* import tanya.memory;
* import tanya.network.socket;
*
* class EchoProtocol : TransmissionControlProtocol
* {
* private DuplexTransport transport;
*
* void received(in ubyte[] data) @nogc
* {
* transport.write(data);
* }
*
* void connected(DuplexTransport transport) @nogc
* {
* this.transport = transport;
* }
*
* void disconnected(SocketException e) @nogc
* {
* }
* }
*
* void main()
* {
* auto address = defaultAllocator.make!InternetAddress("127.0.0.1", cast(ushort) 8192);
*
* version (Windows)
* {
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.inet);
* }
* else
* {
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.inet);
* sock.blocking = false;
* }
*
* sock.bind(address);
* sock.listen(5);
*
* auto io = defaultAllocator.make!ConnectionWatcher(sock);
* io.setProtocol!EchoProtocol;
*
* defaultLoop.start(io);
* defaultLoop.run();
*
* sock.shutdown();
* defaultAllocator.dispose(io);
* defaultAllocator.dispose(sock);
* defaultAllocator.dispose(address);
* }
* ---
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/loop.d,
* tanya/async/loop.d)
*/
module tanya.async.loop;
import core.time;
import std.algorithm.iteration;
import std.algorithm.mutation;
import std.typecons;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.buffer;
import tanya.container.queue;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
version (DisableBackends)
{
}
else version (D_Ddoc)
{
}
else version (linux)
{
import tanya.async.event.epoll;
version = Epoll;
}
else version (Windows)
{
import tanya.async.event.iocp;
version = IOCP;
}
else version (OSX)
{
version = Kqueue;
}
else version (iOS)
{
version = Kqueue;
}
else version (FreeBSD)
{
version = Kqueue;
}
else version (OpenBSD)
{
version = Kqueue;
}
else version (DragonFlyBSD)
{
version = Kqueue;
}
version (unittest)
{
final class TestLoop : Loop
{
override protected bool reify(SocketWatcher watcher,
EventMask oldEvents,
EventMask events) @nogc
{
return true;
}
override protected void poll() @nogc
{
assert(!this.done);
unloop();
}
override protected @property uint maxEvents()
const pure nothrow @safe @nogc
{
return 64U;
}
}
}
/**
* Events.
*/
enum Event : uint
{
none = 0x00, /// No events.
read = 0x01, /// Non-blocking read call.
write = 0x02, /// Non-blocking write call.
accept = 0x04, /// Connection made.
error = 0x80000000, /// Sent when an error occurs.
}
alias EventMask = BitFlags!Event;
/**
* Event loop.
*/
abstract class Loop
{
private bool done = true;
/// Pending watchers.
protected Queue!Watcher pendings;
/**
* Returns: Maximal event count can be got at a time
* (should be supported by the backend).
*/
protected @property uint maxEvents()
const pure nothrow @safe @nogc
{
return 128U;
}
private unittest
{
auto loop = defaultAllocator.make!TestLoop;
assert(loop.maxEvents == 64);
defaultAllocator.dispose(loop);
}
/**
* Initializes the loop.
*/
this() @nogc
{
pendings = Queue!Watcher(MmapPool.instance);
}
/**
* Frees loop internals.
*/
~this() @nogc
{
foreach (w; pendings)
{
MmapPool.instance.dispose(w);
}
}
/**
* Starts the loop.
*/
void run() @nogc
{
this.done = false;
do
{
poll();
// Invoke pendings
foreach (ref w; this.pendings)
{
w.invoke();
}
}
while (!this.done);
}
/**
* Break out of the loop.
*/
void unloop() @safe pure nothrow @nogc
{
this.done = true;
}
private unittest
{
auto loop = defaultAllocator.make!TestLoop;
assert(loop.done);
loop.run();
assert(loop.done);
defaultAllocator.dispose(loop);
}
private unittest
{
auto loop = defaultAllocator.make!TestLoop;
auto watcher = defaultAllocator.make!DummyWatcher;
loop.pendings.enqueue(watcher);
assert(!watcher.invoked);
loop.run();
assert(watcher.invoked);
defaultAllocator.dispose(loop);
defaultAllocator.dispose(watcher);
}
/**
* Start watching.
*
* Params:
* watcher = Watcher.
*/
void start(ConnectionWatcher watcher) @nogc
{
if (watcher.active)
{
return;
}
watcher.active = true;
reify(watcher, EventMask(Event.none), EventMask(Event.accept));
}
/**
* Stop watching.
*
* Params:
* watcher = Watcher.
*/
void stop(ConnectionWatcher watcher) @nogc
{
if (!watcher.active)
{
return;
}
watcher.active = false;
reify(watcher, EventMask(Event.accept), EventMask(Event.none));
}
/**
* Should be called if the backend configuration changes.
*
* Params:
* watcher = Watcher.
* oldEvents = The events were already set.
* events = The events should be set.
*
* Returns: $(D_KEYWORD true) if the operation was successful.
*/
abstract protected bool reify(SocketWatcher watcher,
EventMask oldEvents,
EventMask events) @nogc;
/**
* Returns: The blocking time.
*/
protected @property inout(Duration) blockTime()
inout @safe pure nothrow @nogc
{
// Don't block if we have to do.
return pendings.empty ? blockTime_ : Duration.zero;
}
/**
* Sets the blocking time for IO watchers.
*
* Params:
* blockTime = The blocking time. Cannot be larger than
* $(D_PSYMBOL maxBlockTime).
*/
protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc
in
{
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
assert(!blockTime.isNegative);
}
body
{
blockTime_ = blockTime;
}
private unittest
{
auto loop = defaultAllocator.make!TestLoop;
assert(loop.blockTime == 1.dur!"minutes");
loop.blockTime = 2.dur!"minutes";
assert(loop.blockTime == 2.dur!"minutes");
defaultAllocator.dispose(loop);
}
/**
* Does the actual polling.
*/
abstract protected void poll() @nogc;
/// Maximal block time.
protected Duration blockTime_ = 1.dur!"minutes";
}
/**
* Exception thrown on errors in the event loop.
*/
class BadLoopException : Exception
{
/**
* Params:
* file = The file where the exception occurred.
* line = The line number where the exception occurred.
* next = The previous exception in the chain of exceptions, if any.
*/
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
pure nothrow const @safe @nogc
{
super("Event loop cannot be initialized.", file, line, next);
}
}
/**
* Returns the event loop used by default. If an event loop wasn't set with
* $(D_PSYMBOL defaultLoop) before, $(D_PSYMBOL defaultLoop) will try to
* choose an event loop supported on the system.
*
* Returns: The default event loop.
*/
@property Loop defaultLoop() @nogc
{
if (defaultLoop_ !is null)
{
return defaultLoop_;
}
version (Epoll)
{
defaultLoop_ = MmapPool.instance.make!EpollLoop;
}
else version (IOCP)
{
defaultLoop_ = MmapPool.instance.make!IOCPLoop;
}
else version (Kqueue)
{
import tanya.async.event.kqueue;
defaultLoop_ = MmapPool.instance.make!KqueueLoop;
}
return defaultLoop_;
}
/**
* Sets the default event loop.
*
* This property makes it possible to implement your own backends or event
* loops, for example, if the system is not supported or if you want to
* extend the supported implementation. Just extend $(D_PSYMBOL Loop) and pass
* your implementation to this property.
*
* Params:
* loop = The event loop.
*/
@property void defaultLoop(Loop loop) @nogc
in
{
assert(loop !is null);
}
body
{
defaultLoop_ = loop;
}
private Loop defaultLoop_;
private unittest
{
auto oldLoop = defaultLoop_;
auto loop = defaultAllocator.make!TestLoop;
defaultLoop = loop;
assert(defaultLoop_ is loop);
assert(defaultLoop is loop);
defaultLoop_ = oldLoop;
defaultAllocator.dispose(loop);
}

View File

@ -1,20 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This package provides asynchronous capabilities.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/package.d,
* tanya/async/package.d)
*/
module tanya.async;
public import tanya.async.loop;
public import tanya.async.protocol;
public import tanya.async.transport;
public import tanya.async.watcher;

View File

@ -1,58 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This module contains protocol which handle data in asynchronous
* applications.
*
* When an event from the network arrives, a protocol method gets
* called and can respond to the event.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/protocol.d,
* tanya/async/protocol.d)
*/
module tanya.async.protocol;
import tanya.network.socket;
import tanya.async.transport;
/**
* Common protocol interface.
*/
interface Protocol
{
/**
* Params:
* data = Read data.
*/
void received(in ubyte[] data) @nogc;
/**
* Called when a connection is made.
*
* Params:
* transport = Protocol transport.
*/
void connected(DuplexTransport transport) @nogc;
/**
* Called when a connection is lost.
*
* Params:
* exception = $(D_PSYMBOL Exception) if an error caused
* the disconnect, $(D_KEYWORD null) otherwise.
*/
void disconnected(SocketException exception) @nogc;
}
/**
* Interface for TCP.
*/
interface TransmissionControlProtocol : Protocol
{
}

View File

@ -1,105 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This module contains transports which are responsible for data dilvery
* between two parties of an asynchronous communication.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/transport.d,
* tanya/async/transport.d)
*/
module tanya.async.transport;
import tanya.async.protocol;
import tanya.network.socket;
/**
* Base transport interface.
*/
interface Transport
{
}
/**
* Interface for read-only transports.
*/
interface ReadTransport : Transport
{
}
/**
* Interface for write-only transports.
*/
interface WriteTransport : Transport
{
/**
* Write some data to the transport.
*
* Params:
* data = Data to send.
*/
void write(ubyte[] data) @nogc;
}
/**
* Represents a bidirectional transport.
*/
interface DuplexTransport : ReadTransport, WriteTransport
{
/**
* Returns: Application protocol.
*
* Postcondition: $(D_INLINECODE protocol !is null)
*/
@property Protocol protocol() pure nothrow @safe @nogc
out (protocol)
{
assert(protocol !is null);
}
/**
* Switches the protocol.
*
* The protocol is deallocated by the event loop, it should currently be
* allocated with $(D_PSYMBOL MmapPool).
*
* Params:
* protocol = Application protocol.
*
* Precondition: $(D_INLINECODE protocol !is null)
*/
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
in
{
assert(protocol !is null);
}
/**
* Returns $(D_PARAM true) if the transport is closing or closed.
*/
bool isClosing() const pure nothrow @safe @nogc;
/**
* Close the transport.
*
* Buffered data will be flushed. No more data will be received.
*/
void close() @nogc;
}
/**
* Represents a socket transport.
*/
interface SocketTransport : Transport
{
/**
* Returns: Socket.
*/
@property Socket socket() pure nothrow @safe @nogc;
}

View File

@ -1,134 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Watchers register user's interest in some event.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/watcher.d,
* tanya/async/watcher.d)
*/
module tanya.async.watcher;
import std.functional;
import std.exception;
import tanya.async.loop;
import tanya.async.protocol;
import tanya.async.transport;
import tanya.container.buffer;
import tanya.container.queue;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
/**
* A watcher is an opaque structure that you allocate and register to record
* your interest in some event.
*/
abstract class Watcher
{
/// Whether the watcher is active.
bool active;
/**
* Invoke some action on event.
*/
void invoke() @nogc;
}
version (unittest)
{
final class DummyWatcher : Watcher
{
bool invoked;
override void invoke() @nogc
{
this.invoked = true;
}
}
}
/**
* Socket watcher.
*/
abstract class SocketWatcher : Watcher
{
/// Watched socket.
protected Socket socket_;
/**
* Params:
* socket = Socket.
*
* Precondition: $(D_INLINECODE socket !is null)
*/
this(Socket socket) pure nothrow @safe @nogc
in
{
assert(socket !is null);
}
body
{
socket_ = socket;
}
/**
* Returns: Socket.
*/
@property Socket socket() pure nothrow @safe @nogc
{
return socket_;
}
}
/**
* Connection watcher.
*/
class ConnectionWatcher : SocketWatcher
{
/// Incoming connection queue.
Queue!DuplexTransport incoming;
private Protocol delegate() @nogc protocolFactory;
/**
* Params:
* socket = Socket.
*/
this(Socket socket) @nogc
{
super(socket);
incoming = Queue!DuplexTransport(MmapPool.instance);
}
/**
* Params:
* P = Protocol should be used.
*/
void setProtocol(P : Protocol)() @nogc
{
this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P;
}
/**
* Invokes new connection callback.
*/
override void invoke() @nogc
in
{
assert(protocolFactory !is null, "Protocol isn't set.");
}
body
{
foreach (transport; incoming)
{
transport.protocol = protocolFactory();
transport.protocol.connected(transport);
}
}
}

318
source/tanya/bitmanip.d Normal file
View File

@ -0,0 +1,318 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Bit manipulation.
*
* Copyright: Eugene Wissner 2018-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/bitmanip.d,
* tanya/bitmanip.d)
*/
module tanya.bitmanip;
import tanya.meta.metafunction;
import tanya.meta.trait;
import tanya.meta.transform;
/**
* Determines whether $(D_PARAM E) is a $(D_KEYWORD enum), whose members can be
* used as bit flags.
*
* This is the case if all members of $(D_PARAM E) are integral numbers that
* are either 0 or positive integral powers of 2.
*
* Params:
* E = Some $(D_KEYWORD enum).
*
* Returns: $(D_KEYWORD true) if $(D_PARAM E) contains only bit flags,
* $(D_KEYWORD false) otherwise.
*/
template isBitFlagEnum(E)
{
enum bool isValid(OriginalType!E x) = x == 0
|| (x > 0 && ((x & (x - 1)) == 0));
static if (isIntegral!E)
{
enum bool isBitFlagEnum = allSatisfy!(isValid, EnumMembers!E);
}
else
{
enum bool isBitFlagEnum = false;
}
}
///
@nogc nothrow pure @safe unittest
{
enum Valid
{
none = 0,
one = 1 << 0,
two = 1 << 1,
}
static assert(isBitFlagEnum!Valid);
enum Invalid
{
one,
two,
three,
four,
}
static assert(!isBitFlagEnum!Invalid);
enum Negative
{
one = -1,
two = -2,
}
static assert(!isBitFlagEnum!Negative);
}
/**
* Validates that $(D_PARAM field) contains only bits from $(D_PARAM E).
*
* Params:
* E = Some $(D_KEYWORD enum).
* field = Bit field.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM field) is valid, $(D_KEYWORD false)
* otherwise.
*/
bool containsBitFlags(E)(E field)
if (isBitFlagEnum!E)
{
OriginalType!E fillField()
{
typeof(return) full;
static foreach (member; EnumMembers!E)
{
full |= member;
}
return full;
}
enum OriginalType!E full = fillField();
return (field & ~full) == OriginalType!E.init;
}
///
@nogc nothrow pure @safe unittest
{
enum E
{
one,
two,
three,
}
assert(containsBitFlags(E.one | E.two));
assert(!containsBitFlags(cast(E) 0x8));
}
/**
* Allows to use $(D_KEYWORD enum) values as a set of bit flags.
*
* $(D_PSYMBOL BitFlags) behaves the same as a bit field of type $(D_PARAM E),
* but does additional cheks to ensure that the bit field contains only valid
* values, this is only values from $(D_PARAM E).
*
* Params:
* E = Some $(D_KEYWORD enum).
*/
struct BitFlags(E)
if (isBitFlagEnum!E)
{
private OriginalType!E field;
/**
* Constructs $(D_PSYMBOL BitFlags) from $(D_PARAM field).
*
* Params:
* field = Bits to be set.
*/
this(E field)
{
this.field = field;
}
/**
* Converts $(D_PSYMBOL BitFlags) to a boolean.
*
* It is $(D_KEYWORD true) if any bit is set, $(D_KEYWORD false) otherwise.
*
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL BitFlags) contains any
* set bits, $(D_KEYWORD false) otherwise.
*/
bool opCast(T : bool)()
{
return this.field != 0;
}
/**
* Converts to the original type of $(D_PARAM E) ($(D_KEYWORD int) by
* default).
*
* Returns: $(D_KEYWORD this) as $(D_INLINECODE OriginalType!T).
*/
OriginalType!E opCast(T : OriginalType!E)() const
{
return this.field;
}
/**
* Tests (&), sets (|) or toggles (^) bits.
*
* Params:
* op = Operation.
* that = 0 or more bit flags.
*
* Returns: New $(D_PSYMBOL BitFlags) object.
*/
BitFlags opBinary(string op)(E that) const
if (op == "&" || op == "|" || op == "^")
{
BitFlags result = this;
mixin("return result " ~ op ~ "= that;");
}
/// ditto
BitFlags opBinary(string op)(BitFlags that) const
if (op == "&" || op == "|" || op == "^")
{
BitFlags result = this;
mixin("return result " ~ op ~ "= that;");
}
/// ditto
BitFlags opBinaryRight(string op)(E that) const
if (op == "&" || op == "|" || op == "^")
{
BitFlags result = this;
mixin("return result " ~ op ~ "= that;");
}
/**
* Tests (&), sets (|) or toggles (^) bits.
*
* Params:
* op = Operation.
* that = 0 or more bit flags.
*
* Returns: $(D_KEYWORD this).
*/
ref BitFlags opOpAssign(string op)(E that)
if (op == "&" || op == "|" || op == "^")
{
mixin("this.field " ~ op ~ "= that;");
return this;
}
/// ditto
ref BitFlags opOpAssign(string op)(BitFlags that)
if (op == "&" || op == "|" || op == "^")
{
mixin("this.field " ~ op ~ "= that.field;");
return this;
}
/**
* Inverts all bit flags.
*
* Returns: New $(D_PSYMBOL BitFlags) object with all bits inverted.
*/
BitFlags opUnary(string op : "~")() const
{
BitFlags result;
result.field = ~this.field;
return result;
}
/**
* Assigns a bit field.
*
* Params:
* that = Bit field of type $(D_PARAM E).
*
* Returns: $(D_KEYWORD this).
*/
ref BitFlags opAssign(E that)
{
this.field = that;
return this;
}
/**
* Compares this $(D_PSYMBOL BitFlags) object to another bit field.
*
* Params:
* that = $(D_PSYMBOL BitFlags) object or a bit field of type
* $(D_PARAM E).
*
* Returns: $(D_KEYWORD true) if $(D_KEYWORD this) and $(D_PARAM that)
* contain the same bits ,$(D_KEYWORD false) otherwise.
*/
bool opEquals(E that) const
{
return this.field == that;
}
/// ditto
bool opEquals(BitFlags that) const
{
return this.field == that.field;
}
/**
* Generates a hash value of this object.
*
* Returns: Hash value.
*/
size_t toHash() const
{
return cast(size_t) this.field;
}
}
/**
* Creates a $(D_PSYMBOL BitFlags) object initialized with $(D_PARAM field).
*
* Params:
* E = Some $(D_KEYWORD enum).
* field = Bits to be set.
*/
BitFlags!E bitFlags(E)(E field)
if (isBitFlagEnum!E)
{
return BitFlags!E(field);
}
///
@nogc nothrow pure @safe unittest
{
enum E
{
one = 1 << 0,
two = 1 << 1,
three = 1 << 2,
}
// Construct with E.one and E.two set
auto flags = bitFlags(E.one | E.two);
// Test wheter E.one is set
assert(flags & E.one);
// Toggle E.one
flags ^= E.one;
assert(!(flags & E.one));
// Set E.three
flags |= E.three;
assert(flags & E.three);
// Clear E.three
flags &= ~E.three;
assert(!(flags & E.three));
}

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
/**
* This module contains buffers designed for C-style input/output APIs.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
@ -14,20 +14,19 @@
*/
module tanya.container.buffer;
import std.traits;
import tanya.memory;
import tanya.memory.allocator;
import tanya.meta.trait;
version (unittest)
{
private int fillBuffer(ubyte[] buffer,
in size_t size,
int start = 0,
int end = 10) @nogc pure nothrow
in
{
assert(start < end);
}
body
do
{
auto numberRead = end - start;
for (ubyte i; i < numberRead; ++i)
@ -67,16 +66,16 @@ struct ReadBuffer(T = ubyte)
private size_t ring;
/// Available space.
private immutable size_t minAvailable = 1024;
private size_t minAvailable = 1024;
/// Size by which the buffer will grow.
private immutable size_t blockSize = 8192;
private size_t blockSize = 8192;
invariant
{
assert(length_ <= buffer_.length);
assert(blockSize > 0);
assert(minAvailable > 0);
assert(this.length_ <= this.buffer_.length);
assert(this.blockSize > 0);
assert(this.minAvailable > 0);
}
/**
@ -90,23 +89,23 @@ struct ReadBuffer(T = ubyte)
* $(D_PSYMBOL free) < $(D_PARAM minAvailable)).
* allocator = Allocator.
*/
this(in size_t size,
in size_t minAvailable = 1024,
this(size_t size,
size_t minAvailable = 1024,
shared Allocator allocator = defaultAllocator) @trusted
{
this(allocator);
this.minAvailable = minAvailable;
this.blockSize = size;
buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
this.buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
}
/// Ditto.
/// ditto
this(shared Allocator allocator)
in
{
assert(allocator_ is null);
}
body
do
{
allocator_ = allocator;
}
@ -116,11 +115,11 @@ struct ReadBuffer(T = ubyte)
*/
~this() @trusted
{
allocator.deallocate(buffer_);
allocator.deallocate(this.buffer_);
}
///
unittest
@nogc nothrow pure @safe unittest
{
ReadBuffer!ubyte b;
assert(b.capacity == 0);
@ -132,7 +131,7 @@ struct ReadBuffer(T = ubyte)
*/
@property size_t capacity() const
{
return buffer_.length;
return this.buffer_.length;
}
/**
@ -140,10 +139,10 @@ struct ReadBuffer(T = ubyte)
*/
@property size_t length() const
{
return length_ - start;
return this.length_ - start;
}
/// Ditto.
/// ditto
alias opDollar = length;
/**
@ -153,7 +152,7 @@ struct ReadBuffer(T = ubyte)
*/
void clear()
{
start = length_ = ring;
start = this.length_ = ring;
}
/**
@ -165,7 +164,7 @@ struct ReadBuffer(T = ubyte)
}
///
unittest
@nogc nothrow pure @system unittest
{
ReadBuffer!ubyte b;
size_t numberRead;
@ -173,7 +172,7 @@ struct ReadBuffer(T = ubyte)
assert(b.free == 0);
// Fills the buffer with values 0..10
numberRead = fillBuffer(b[], b.free, 0, 10);
numberRead = fillBuffer(b[], 0, 10);
b += numberRead;
assert(b.free == b.blockSize - numberRead);
b.clear();
@ -188,23 +187,23 @@ struct ReadBuffer(T = ubyte)
*
* Returns: $(D_KEYWORD this).
*/
ref ReadBuffer opOpAssign(string op)(in size_t length)
ref ReadBuffer opOpAssign(string op)(size_t length)
if (op == "+")
{
length_ += length;
this.length_ += length;
ring = start;
return this;
}
///
unittest
@nogc nothrow pure @system unittest
{
ReadBuffer!ubyte b;
size_t numberRead;
ubyte[] result;
// Fills the buffer with values 0..10
numberRead = fillBuffer(b[], b.free, 0, 10);
numberRead = fillBuffer(b[], 0, 10);
b += numberRead;
result = b[0 .. $];
@ -214,10 +213,10 @@ struct ReadBuffer(T = ubyte)
b.clear();
// It shouldn't overwrite, but append another 5 bytes to the buffer
numberRead = fillBuffer(b[], b.free, 0, 10);
numberRead = fillBuffer(b[], 0, 10);
b += numberRead;
numberRead = fillBuffer(b[], b.free, 20, 25);
numberRead = fillBuffer(b[], 20, 25);
b += numberRead;
result = b[0..$];
@ -235,9 +234,9 @@ struct ReadBuffer(T = ubyte)
*
* Returns: Array between $(D_PARAM start) and $(D_PARAM end).
*/
T[] opSlice(in size_t start, in size_t end)
T[] opSlice(size_t start, size_t end)
{
return buffer_[this.start + start .. this.start + end];
return this.buffer_[this.start + start .. this.start + end];
}
/**
@ -251,35 +250,36 @@ struct ReadBuffer(T = ubyte)
{
if (start > 0)
{
auto ret = buffer_[0 .. start];
auto ret = this.buffer_[0 .. start];
ring = 0;
return ret;
}
else
{
if (capacity - length < minAvailable)
if (capacity - length < this.minAvailable)
{
void[] buf = buffer_;
immutable cap = capacity;
void[] buf = this.buffer_;
const cap = capacity;
() @trusted {
allocator.reallocate(buf, (cap + blockSize) * T.sizeof);
buffer_ = cast(T[]) buf;
allocator.reallocate(buf,
(cap + this.blockSize) * T.sizeof);
this.buffer_ = cast(T[]) buf;
}();
}
ring = length_;
return buffer_[length_ .. $];
ring = this.length_;
return this.buffer_[this.length_ .. $];
}
}
///
unittest
@nogc nothrow pure @system unittest
{
ReadBuffer!ubyte b;
size_t numberRead;
ubyte[] result;
// Fills the buffer with values 0..10
numberRead = fillBuffer(b[], b.free, 0, 10);
numberRead = fillBuffer(b[], 0, 10);
b += numberRead;
assert(b.length == 10);
@ -293,11 +293,6 @@ struct ReadBuffer(T = ubyte)
mixin DefaultAllocator;
}
private unittest
{
static assert(is(ReadBuffer!int));
}
/**
* Circular, self-expanding buffer with overflow support. Can be used with
* functions returning the number of the transferred bytes.
@ -323,16 +318,17 @@ struct WriteBuffer(T = ubyte)
private size_t ring;
/// Size by which the buffer will grow.
private immutable size_t blockSize;
private const size_t blockSize;
/// The position of the free area in the buffer.
private size_t position;
invariant
{
assert(blockSize > 0);
// Position can refer to an element outside the buffer if the buffer is full.
assert(position <= buffer_.length);
assert(this.blockSize > 0);
// Position can refer to an element outside the buffer if the buffer is
// full.
assert(this.position <= this.buffer_.length);
}
/**
@ -343,18 +339,18 @@ struct WriteBuffer(T = ubyte)
*
* Precondition: $(D_INLINECODE size > 0 && allocator !is null)
*/
this(in size_t size, shared Allocator allocator = defaultAllocator) @trusted
this(size_t size, shared Allocator allocator = defaultAllocator) @trusted
in
{
assert(size > 0);
assert(allocator !is null);
}
body
do
{
blockSize = size;
this.blockSize = size;
ring = size - 1;
allocator_ = allocator;
buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
this.buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
}
@disable this();
@ -364,7 +360,7 @@ struct WriteBuffer(T = ubyte)
*/
~this()
{
allocator.deallocate(buffer_);
allocator.deallocate(this.buffer_);
}
/**
@ -372,7 +368,7 @@ struct WriteBuffer(T = ubyte)
*/
@property size_t capacity() const
{
return buffer_.length;
return this.buffer_.length;
}
/**
@ -385,21 +381,21 @@ struct WriteBuffer(T = ubyte)
*/
@property size_t length() const
{
if (position > ring || position < start) // Buffer overflowed
if (this.position > ring || this.position < start) // Buffer overflowed
{
return ring - start + 1;
}
else
{
return position - start;
return this.position - start;
}
}
/// Ditto.
/// ditto
alias opDollar = length;
///
unittest
@nogc nothrow pure @system unittest
{
auto b = WriteBuffer!ubyte(4);
ubyte[3] buf = [48, 23, 255];
@ -434,61 +430,62 @@ struct WriteBuffer(T = ubyte)
* Params:
* buffer = Buffer chunk got with $(D_PSYMBOL opIndex).
*/
ref WriteBuffer opOpAssign(string op)(in T[] buffer)
ref WriteBuffer opOpAssign(string op)(const T[] buffer)
if (op == "~")
{
size_t end, start;
if (position >= this.start && position <= ring)
if (this.position >= this.start && this.position <= ring)
{
auto afterRing = ring + 1;
end = position + buffer.length;
end = this.position + buffer.length;
if (end > afterRing)
{
end = afterRing;
}
start = end - position;
buffer_[position .. end] = buffer[0 .. start];
start = end - this.position;
this.buffer_[this.position .. end] = buffer[0 .. start];
if (end == afterRing)
{
position = this.start == 0 ? afterRing : 0;
this.position = this.start == 0 ? afterRing : 0;
}
else
{
position = end;
this.position = end;
}
}
// Check if we have some free space at the beginning
if (start < buffer.length && position < this.start)
if (start < buffer.length && this.position < this.start)
{
end = position + buffer.length - start;
end = this.position + buffer.length - start;
if (end > this.start)
{
end = this.start;
}
auto areaEnd = end - position + start;
buffer_[position .. end] = buffer[start .. areaEnd];
position = end == this.start ? ring + 1 : end - position;
auto areaEnd = end - this.position + start;
this.buffer_[this.position .. end] = buffer[start .. areaEnd];
this.position = end == this.start ? ring + 1 : end - this.position;
start = areaEnd;
}
// And if we still haven't found any place, save the rest in the overflow area
if (start < buffer.length)
{
end = position + buffer.length - start;
end = this.position + buffer.length - start;
if (end > capacity)
{
auto newSize = (end / blockSize * blockSize + blockSize) * T.sizeof;
const newSize = end / this.blockSize * this.blockSize
+ this.blockSize;
() @trusted {
void[] buf = buffer_;
allocator.reallocate(buf, newSize);
buffer_ = cast(T[]) buf;
void[] buf = this.buffer_;
allocator.reallocate(buf, newSize * T.sizeof);
this.buffer_ = cast(T[]) buf;
}();
}
buffer_[position .. end] = buffer[start .. $];
position = end;
this.buffer_[this.position .. end] = buffer[start .. $];
this.position = end;
if (this.start == 0)
{
ring = capacity - 1;
@ -498,42 +495,6 @@ struct WriteBuffer(T = ubyte)
return this;
}
///
unittest
{
auto b = WriteBuffer!ubyte(4);
ubyte[3] buf = [48, 23, 255];
b ~= buf;
assert(b.capacity == 4);
assert(b.buffer_[0] == 48 && b.buffer_[1] == 23 && b.buffer_[2] == 255);
b += 2;
b ~= buf;
assert(b.capacity == 4);
assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
&& b.buffer_[2] == 255 && b.buffer_[3] == 48);
b += 2;
b ~= buf;
assert(b.capacity == 8);
assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
&& b.buffer_[2] == 48 && b.buffer_[3] == 23 && b.buffer_[4] == 255);
}
///
unittest
{
auto b = WriteBuffer!ubyte(2);
ubyte[3] buf = [48, 23, 255];
b ~= buf;
assert(b.start == 0);
assert(b.capacity == 4);
assert(b.ring == 3);
assert(b.position == 3);
}
/**
* Sets how many bytes were written. It will shrink the buffer
* appropriately. Always call it after $(D_PSYMBOL opIndex).
@ -543,13 +504,13 @@ struct WriteBuffer(T = ubyte)
*
* Returns: $(D_KEYWORD this).
*/
ref WriteBuffer opOpAssign(string op)(in size_t length)
ref WriteBuffer opOpAssign(string op)(size_t length)
if (op == "+")
in
{
assert(length <= this.length);
}
body
do
{
auto afterRing = ring + 1;
auto oldStart = start;
@ -558,42 +519,42 @@ struct WriteBuffer(T = ubyte)
{
return this;
}
else if (position <= afterRing)
else if (this.position <= afterRing)
{
start += length;
if (start > 0 && position == afterRing)
if (start > 0 && this.position == afterRing)
{
position = oldStart;
this.position = oldStart;
}
}
else
{
auto overflow = position - afterRing;
auto overflow = this.position - afterRing;
if (overflow > length)
{
immutable afterLength = afterRing + length;
buffer_[start .. start + length] = buffer_[afterRing .. afterLength];
buffer_[afterRing .. afterLength] = buffer_[afterLength .. position];
position -= length;
const afterLength = afterRing + length;
this.buffer_[start .. start + length] = this.buffer_[afterRing .. afterLength];
this.buffer_[afterRing .. afterLength] = this.buffer_[afterLength .. this.position];
this.position -= length;
}
else if (overflow == length)
{
buffer_[start .. start + overflow] = buffer_[afterRing .. position];
position -= overflow;
this.buffer_[start .. start + overflow] = this.buffer_[afterRing .. this.position];
this.position -= overflow;
}
else
{
buffer_[start .. start + overflow] = buffer_[afterRing .. position];
position = overflow;
this.buffer_[start .. start + overflow] = this.buffer_[afterRing .. this.position];
this.position = overflow;
}
start += length;
if (start == position)
if (start == this.position)
{
if (position != afterRing)
if (this.position != afterRing)
{
position = 0;
this.position = 0;
}
start = 0;
ring = capacity - 1;
@ -607,7 +568,7 @@ struct WriteBuffer(T = ubyte)
}
///
unittest
@nogc nothrow pure @system unittest
{
auto b = WriteBuffer!ubyte(6);
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
@ -633,22 +594,20 @@ struct WriteBuffer(T = ubyte)
*
* Returns: A chunk of data buffer.
*/
T[] opSlice(in size_t start, in size_t end)
T[] opSlice(size_t start, size_t end)
{
immutable internStart = this.start + start;
if (position > ring || position < start) // Buffer overflowed
if (this.position > ring || this.position < start) // Buffer overflowed
{
return buffer_[this.start .. ring + 1 - length + end];
return this.buffer_[this.start .. ring + 1 - length + end];
}
else
{
return buffer_[this.start .. this.start + end];
return this.buffer_[this.start .. this.start + end];
}
}
///
unittest
@nogc nothrow pure @system unittest
{
auto b = WriteBuffer!ubyte(6);
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
@ -686,7 +645,36 @@ struct WriteBuffer(T = ubyte)
mixin DefaultAllocator;
}
private unittest
@nogc nothrow pure @system unittest
{
static assert(is(typeof(WriteBuffer!int(5))));
auto b = WriteBuffer!ubyte(4);
ubyte[3] buf = [48, 23, 255];
b ~= buf;
assert(b.capacity == 4);
assert(b.buffer_[0] == 48 && b.buffer_[1] == 23 && b.buffer_[2] == 255);
b += 2;
b ~= buf;
assert(b.capacity == 4);
assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
&& b.buffer_[2] == 255 && b.buffer_[3] == 48);
b += 2;
b ~= buf;
assert(b.capacity == 8);
assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
&& b.buffer_[2] == 48 && b.buffer_[3] == 23 && b.buffer_[4] == 255);
}
@nogc nothrow pure @system unittest
{
auto b = WriteBuffer!ubyte(2);
ubyte[3] buf = [48, 23, 255];
b ~= buf;
assert(b.start == 0);
assert(b.capacity == 4);
assert(b.ring == 3);
assert(b.position == 3);
}

View File

@ -5,7 +5,7 @@
/*
* Internal package used by containers that rely on entries/nodes.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
@ -14,7 +14,11 @@
*/
module tanya.container.entry;
import std.traits;
import tanya.container.array;
import tanya.memory.allocator;
import tanya.memory.lifetime;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.typecons;
package struct SEntry(T)
@ -35,17 +39,6 @@ package struct DEntry(T)
DEntry* next, prev;
}
package struct HashEntry(K, V)
{
this(ref K key, ref V value)
{
this.pair = Pair!(K, V)(key, value);
}
Pair!(K, V) pair;
HashEntry* next;
}
package enum BucketStatus : byte
{
deleted = -1,
@ -53,56 +46,283 @@ package enum BucketStatus : byte
used = 1,
}
package struct Bucket(T)
package struct Bucket(K, V = void)
{
@property void content(ref T content)
static if (is(V == void))
{
this.content_ = content;
K key_;
}
else
{
alias KV = Tuple!(K, "key", V, "value");
KV kv;
}
BucketStatus status = BucketStatus.empty;
this()(ref K key)
{
this.key = key;
}
@property void key()(ref K key)
{
this.key() = key;
this.status = BucketStatus.used;
}
@property ref inout(T) content() inout
@property ref inout(K) key() inout
{
return this.content_;
static if (is(V == void))
{
return this.key_;
}
else
{
return this.kv.key;
}
}
bool opEquals(ref T content)
void moveKey(ref K key)
{
if (this.status == BucketStatus.used && this.content == content)
{
return true;
}
return false;
move(key, this.key());
this.status = BucketStatus.used;
}
bool opEquals(ref const T content) const
bool opEquals(T)(ref const T key) const
{
if (this.status == BucketStatus.used && this.content == content)
{
return true;
}
return false;
return this.status == BucketStatus.used && this.key == key;
}
bool opEquals(ref typeof(this) that)
bool opEquals(ref const(typeof(this)) that) const
{
return this.content == that.content && this.status == that.status;
}
bool opEquals(ref typeof(this) that) const
{
return this.content == that.content && this.status == that.status;
return key == that.key && this.status == that.status;
}
void remove()
{
static if (hasElaborateDestructor!T)
static if (hasElaborateDestructor!K)
{
destroy(this.content);
destroy(key);
}
this.status = BucketStatus.deleted;
}
T content_;
BucketStatus status = BucketStatus.empty;
}
// Possible sizes for the hash-based containers.
package static immutable size_t[33] primes = [
0, 3, 7, 13, 23, 37, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289,
24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469,
12582917, 25165843, 50331653, 100663319, 201326611, 402653189,
805306457, 1610612741, 3221225473
];
package(tanya.container) struct HashArray(alias hasher, K, V = void)
{
alias Key = K;
alias Value = V;
alias Bucket = .Bucket!(Key, Value);
alias Buckets = Array!Bucket;
Buckets array;
size_t lengthIndex;
size_t length;
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
do
{
this.array = Buckets(allocator);
}
this(T)(ref T data, shared Allocator allocator)
if (is(Unqual!T == HashArray))
in
{
assert(allocator !is null);
}
do
{
this.array = Buckets(data.array, allocator);
this.lengthIndex = data.lengthIndex;
this.length = data.length;
}
// Move constructor
void move(ref HashArray data, shared Allocator allocator)
in
{
assert(allocator !is null);
}
do
{
this.array = Buckets(.move(data.array), allocator);
this.lengthIndex = data.lengthIndex;
this.length = data.length;
}
void swap(ref HashArray data)
{
.swap(this.array, data.array);
.swap(this.lengthIndex, data.lengthIndex);
.swap(this.length, data.length);
}
void opAssign()(ref typeof(this) that)
{
this.array = that.array;
this.lengthIndex = that.lengthIndex;
this.length = that.length;
}
@property size_t bucketCount() const
{
return primes[this.lengthIndex];
}
/*
* Returns bucket position for `hash`. `0` may mean the 0th position or an
* empty `buckets` array.
*/
size_t locateBucket(T)(ref const T key) const
{
return this.array.length == 0 ? 0 : hasher(key) % bucketCount;
}
/*
* If the key doesn't already exists, returns an empty bucket the key can
* be inserted in and adjusts the element count. Otherwise returns the
* bucket containing the key.
*/
ref Bucket insert(ref Key key)
{
const newLengthIndex = this.lengthIndex + 1;
if (newLengthIndex != primes.length)
{
foreach (ref e; this.array[locateBucket(key) .. $])
{
if (e == key)
{
return e;
}
else if (e.status != BucketStatus.used)
{
++this.length;
return e;
}
}
this.rehashToSize(newLengthIndex);
}
foreach (ref e; this.array[locateBucket(key) .. $])
{
if (e == key)
{
return e;
}
else if (e.status != BucketStatus.used)
{
++this.length;
return e;
}
}
this.array.length = this.array.length + 1;
++this.length;
return this.array[$ - 1];
}
// Takes an index in the primes array.
void rehashToSize(const size_t n)
in
{
assert(n < primes.length);
}
do
{
auto storage = typeof(this.array)(primes[n], this.array.allocator);
DataLoop: foreach (ref e1; this.array[])
{
if (e1.status == BucketStatus.used)
{
auto bucketPosition = hasher(e1.key) % primes[n];
foreach (ref e2; storage[bucketPosition .. $])
{
if (e2.status != BucketStatus.used) // Insert the key
{
.move(e1, e2);
continue DataLoop;
}
}
storage.insertBack(.move(e1));
}
}
.move(storage, this.array);
this.lengthIndex = n;
}
void rehash(const size_t n)
{
size_t lengthIndex;
for (; lengthIndex < primes.length; ++lengthIndex)
{
if (primes[lengthIndex] >= n)
{
break;
}
}
if (lengthIndex > this.lengthIndex)
{
this.rehashToSize(lengthIndex);
}
}
@property size_t capacity() const
{
return this.array.length;
}
void clear()
{
this.array.clear();
this.length = 0;
}
size_t remove(ref Key key)
{
foreach (ref e; this.array[locateBucket(key) .. $])
{
if (e == key) // Found.
{
e.remove();
--this.length;
return 1;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return 0;
}
bool opBinaryRight(string op : "in", T)(ref const T key) const
{
foreach (ref e; this.array[locateBucket(key) .. $])
{
if (e == key) // Found.
{
return true;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return false;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
/**
* Abstract data types whose instances are collections of other objects.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
@ -16,29 +16,7 @@ module tanya.container;
public import tanya.container.array;
public import tanya.container.buffer;
public import tanya.container.set;
public import tanya.container.hashtable;
public import tanya.container.list;
public import tanya.container.set;
public import tanya.container.string;
public import tanya.container.queue;
/**
* Thrown if $(D_PSYMBOL Set) cannot insert a new element because the container
* is full.
*/
class HashContainerFullException : Exception
{
/**
* Params:
* msg = The message for the exception.
* file = The file where the exception occurred.
* line = The line number where the exception occurred.
* next = The previous exception in the chain of exceptions, if any.
*/
this(string msg,
string file = __FILE__,
size_t line = __LINE__,
Throwable next = null) @nogc @safe pure nothrow
{
super(msg, file, line, next);
}
}

View File

@ -1,290 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* FIFO queue.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/container/queue.d,
* tanya/container/queue.d)
*/
module tanya.container.queue;
import core.exception;
import std.traits;
import std.algorithm.mutation;
import tanya.container.entry;
import tanya.memory;
/**
* FIFO queue.
*
* Params:
* T = Content type.
*/
struct Queue(T)
{
/**
* Removes all elements from the queue.
*/
~this()
{
while (!empty)
{
dequeue();
}
}
/**
* Returns how many elements are in the queue. It iterates through the queue
* to count the elements.
*
* Returns: How many elements are in the queue.
*/
size_t length() const
{
size_t len;
for (const(SEntry!T)* i = first; i !is null; i = i.next)
{
++len;
}
return len;
}
///
unittest
{
Queue!int q;
assert(q.length == 0);
q.enqueue(5);
assert(q.length == 1);
q.enqueue(4);
assert(q.length == 2);
q.enqueue(9);
assert(q.length == 3);
q.dequeue();
assert(q.length == 2);
q.dequeue();
assert(q.length == 1);
q.dequeue();
assert(q.length == 0);
}
private void enqueueEntry(ref SEntry!T* entry)
{
if (empty)
{
first = rear = entry;
}
else
{
rear.next = entry;
rear = rear.next;
}
}
private SEntry!T* allocateEntry()
{
auto temp = cast(SEntry!T*) allocator.allocate(SEntry!T.sizeof);
if (temp is null)
{
onOutOfMemoryError();
}
return temp;
}
/**
* Inserts a new element.
*
* Params:
* x = New element.
*/
void enqueue(ref T x)
{
auto temp = allocateEntry();
*temp = SEntry!T.init;
temp.content = x;
enqueueEntry(temp);
}
/// Ditto.
void enqueue(T x)
{
auto temp = allocateEntry();
moveEmplace(x, (*temp).content);
(*temp).next = null;
enqueueEntry(temp);
}
///
unittest
{
Queue!int q;
assert(q.empty);
q.enqueue(8);
q.enqueue(9);
assert(q.dequeue() == 8);
assert(q.dequeue() == 9);
}
/**
* Returns: $(D_KEYWORD true) if the queue is empty.
*/
@property bool empty() const
{
return first is null;
}
///
unittest
{
Queue!int q;
int value = 7;
assert(q.empty);
q.enqueue(value);
assert(!q.empty);
}
/**
* Move the position to the next element.
*
* Returns: Dequeued element.
*/
T dequeue()
in
{
assert(!empty);
}
body
{
auto n = first.next;
T ret = move(first.content);
allocator.dispose(first);
first = n;
return ret;
}
///
unittest
{
Queue!int q;
q.enqueue(8);
q.enqueue(9);
assert(q.dequeue() == 8);
assert(q.dequeue() == 9);
}
/**
* $(D_KEYWORD foreach) iteration. The elements will be automatically
* dequeued.
*
* Params:
* dg = $(D_KEYWORD foreach) body.
*
* Returns: The value returned from $(D_PARAM dg).
*/
int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
{
int result;
for (size_t i = 0; !empty; ++i)
{
auto e = dequeue();
if ((result = dg(i, e)) != 0)
{
return result;
}
}
return result;
}
/// Ditto.
int opApply(scope int delegate(ref T) @nogc dg)
{
int result;
while (!empty)
{
auto e = dequeue();
if ((result = dg(e)) != 0)
{
return result;
}
}
return result;
}
///
unittest
{
Queue!int q;
size_t j;
q.enqueue(5);
q.enqueue(4);
q.enqueue(9);
foreach (i, e; q)
{
assert(i != 2 || e == 9);
assert(i != 1 || e == 4);
assert(i != 0 || e == 5);
++j;
}
assert(j == 3);
assert(q.empty);
j = 0;
q.enqueue(5);
q.enqueue(4);
q.enqueue(9);
foreach (e; q)
{
assert(j != 2 || e == 9);
assert(j != 1 || e == 4);
assert(j != 0 || e == 5);
++j;
}
assert(j == 3);
assert(q.empty);
}
private SEntry!T* first;
private SEntry!T* rear;
mixin DefaultAllocator;
}
///
unittest
{
Queue!int q;
q.enqueue(5);
assert(!q.empty);
q.enqueue(4);
q.enqueue(9);
assert(q.dequeue() == 5);
foreach (i, ref e; q)
{
assert(i != 0 || e == 4);
assert(i != 1 || e == 9);
}
assert(q.empty);
}

View File

@ -6,7 +6,7 @@
* This module implements a $(D_PSYMBOL Set) container that stores unique
* values without any particular order.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
@ -15,27 +15,31 @@
*/
module tanya.container.set;
import std.algorithm.mutation;
import std.traits;
import tanya.container;
import tanya.container.array;
import tanya.container.entry;
import tanya.memory;
import tanya.hash.lookup;
import tanya.memory.allocator;
import tanya.memory.lifetime;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range.primitive;
/**
* Bidirectional range that iterates over the $(D_PSYMBOL Set)'s values.
*
* Params:
* E = Element type.
* T = Type of the internal hash storage.
*/
struct Range(E)
struct Range(T)
{
static if (isMutable!E)
private alias E = CopyConstness!(T, T.Key);
static if (isMutable!T)
{
private alias DataRange = Array!(Bucket!(Unqual!E)).Range;
private alias DataRange = T.array.Range;
}
else
{
private alias DataRange = Array!(Bucket!(Unqual!E)).ConstRange;
private alias DataRange = T.array.ConstRange;
}
private DataRange dataRange;
@ -64,66 +68,64 @@ struct Range(E)
return this.dataRange.empty();
}
@property void popFront()
void popFront()
in
{
assert(!this.dataRange.empty);
assert(!empty);
assert(this.dataRange.front.status == BucketStatus.used);
}
out
{
assert(this.dataRange.empty
|| this.dataRange.back.status == BucketStatus.used);
assert(empty || this.dataRange.back.status == BucketStatus.used);
}
body
do
{
do
{
dataRange.popFront();
this.dataRange.popFront();
}
while (!dataRange.empty && dataRange.front.status != BucketStatus.used);
while (!empty && dataRange.front.status != BucketStatus.used);
}
@property void popBack()
void popBack()
in
{
assert(!this.dataRange.empty);
assert(!empty);
assert(this.dataRange.back.status == BucketStatus.used);
}
out
{
assert(this.dataRange.empty
|| this.dataRange.back.status == BucketStatus.used);
assert(empty || this.dataRange.back.status == BucketStatus.used);
}
body
do
{
do
{
dataRange.popBack();
this.dataRange.popBack();
}
while (!dataRange.empty && dataRange.back.status != BucketStatus.used);
while (!empty && dataRange.back.status != BucketStatus.used);
}
@property ref inout(E) front() inout
in
{
assert(!this.dataRange.empty);
assert(!empty);
assert(this.dataRange.front.status == BucketStatus.used);
}
body
do
{
return dataRange.front.content;
return this.dataRange.front.key;
}
@property ref inout(E) back() inout
in
{
assert(!this.dataRange.empty);
assert(!empty);
assert(this.dataRange.back.status == BucketStatus.used);
}
body
do
{
return dataRange.back.content;
return this.dataRange.back.key;
}
Range opIndex()
@ -131,7 +133,7 @@ struct Range(E)
return typeof(return)(this.dataRange[]);
}
Range!(const E) opIndex() const
Range!(const T) opIndex() const
{
return typeof(return)(this.dataRange[]);
}
@ -144,25 +146,33 @@ struct Range(E)
* This $(D_PSYMBOL Set) is implemented using closed hashing. Hash collisions
* are resolved with linear probing.
*
* Currently works only with integral types.
* $(D_PARAM T) should be hashable with $(D_PARAM hasher). $(D_PARAM hasher) is
* a callable that accepts an argument of type $(D_PARAM T) and returns a hash
* value for it ($(D_KEYWORD size_t)).
*
* Params:
* T = Element type.
* hasher = Hash function for $(D_PARAM T).
*/
struct Set(T)
if (isIntegral!T || is(Unqual!T == bool))
struct Set(T, alias hasher = hash)
if (isHashFunction!(hasher, T))
{
/// The range types for $(D_PSYMBOL Set).
alias Range = .Range!T;
private alias HashArray = .HashArray!(hasher, T);
private alias Buckets = HashArray.Buckets;
/// Ditto.
alias ConstRange = .Range!(const T);
private HashArray data;
/// The range types for $(D_PSYMBOL Set).
alias Range = .Range!HashArray;
/// ditto
alias ConstRange = .Range!(const HashArray);
invariant
{
assert(this.lengthIndex < primes.length);
assert(this.data.length == 0
|| this.data.length == primes[this.lengthIndex]);
assert(this.data.lengthIndex < primes.length);
assert(this.data.array.length == 0
|| this.data.array.length == primes[this.data.lengthIndex]);
}
/**
@ -174,39 +184,33 @@ struct Set(T)
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
this(const size_t n, shared Allocator allocator = defaultAllocator)
this(size_t n, shared Allocator allocator = defaultAllocator)
in
{
assert(allocator !is null);
}
body
do
{
this(allocator);
rehash(n);
this.data.rehash(n);
}
/// Ditto.
///
@nogc nothrow pure @safe unittest
{
auto set = Set!int(5);
assert(set.capacity == 7);
}
/// ditto
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
do
{
this.data = typeof(this.data)(allocator);
}
///
unittest
{
{
auto set = Set!int(defaultAllocator);
assert(set.capacity == 0);
}
{
auto set = Set!int(8);
assert(set.capacity == 13);
}
this.data = HashArray(allocator);
}
/**
@ -219,6 +223,8 @@ struct Set(T)
* S = Source set type.
* init = Source set.
* allocator = Allocator.
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
this(S)(ref S init, shared Allocator allocator = defaultAllocator)
if (is(Unqual!S == Set))
@ -226,23 +232,85 @@ struct Set(T)
{
assert(allocator !is null);
}
body
do
{
this.data = typeof(this.data)(init.data, allocator);
this.data = HashArray(init.data, allocator);
}
/// Ditto.
/// ditto
this(S)(S init, shared Allocator allocator = defaultAllocator)
if (is(S == Set))
in
{
assert(allocator !is null);
}
body
do
{
this.data = typeof(this.data)(move(init.data), allocator);
this.lengthIndex = init.lengthIndex;
init.lengthIndex = 0;
this.data.move(init.data, allocator);
}
/**
* Initializes the set from a forward range.
*
* Params:
* R = Range type.
* range = Forward range.
* allocator = Allocator.
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
this(R)(scope R range, shared Allocator allocator = defaultAllocator)
if (isForwardRange!R
&& isImplicitlyConvertible!(ElementType!R, T)
&& !isInfinite!R)
in
{
assert(allocator !is null);
}
do
{
this(allocator);
insert(range);
}
///
@nogc nothrow pure @safe unittest
{
int[2] range = [1, 2];
Set!int set = Set!int(range[]);
assert(1 in set);
assert(2 in set);
}
/**
* Initializes the set from a static array.
*
* Params:
* n = Array size.
* array = Static array.
* allocator = Allocator.
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
this(size_t n)(T[n] array, shared Allocator allocator = defaultAllocator)
in
{
assert(allocator !is null);
}
do
{
this(allocator);
insert(array[]);
}
///
@nogc nothrow pure @safe unittest
{
Set!int set = Set!int([1, 2]);
assert(1 in set);
assert(2 in set);
}
/**
@ -261,16 +329,14 @@ struct Set(T)
if (is(Unqual!S == Set))
{
this.data = that.data;
this.lengthIndex = that.lengthIndex;
return this;
}
/// Ditto.
/// ditto
ref typeof(this) opAssign(S)(S that) @trusted
if (is(S == Set))
{
swap(this.data, that.data);
swap(this.lengthIndex, that.lengthIndex);
this.data.swap(that.data);
return this;
}
@ -284,9 +350,9 @@ struct Set(T)
{
assert(allocator !is null);
}
body
do
{
return cast(shared Allocator) this.data.allocator;
return this.data.array.allocator;
}
/**
@ -300,11 +366,11 @@ struct Set(T)
*/
@property size_t capacity() const
{
return this.data.length;
return this.data.capacity;
}
///
unittest
@nogc nothrow pure @safe unittest
{
Set!int set;
assert(set.capacity == 0);
@ -320,19 +386,11 @@ struct Set(T)
*/
@property size_t length() const
{
size_t count;
foreach (ref e; this.data[])
{
if (e.status == BucketStatus.used)
{
++count;
}
}
return count;
return this.data.length;
}
///
unittest
@nogc nothrow pure @safe unittest
{
Set!int set;
assert(set.length == 0);
@ -341,81 +399,63 @@ struct Set(T)
assert(set.length == 1);
}
private static const size_t[41] primes = [
3, 7, 13, 23, 29, 37, 53, 71, 97, 131, 163, 193, 239, 293, 389, 521,
769, 919, 1103, 1327, 1543, 2333, 3079, 4861, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469,
12582917, 25165843, 139022417, 282312799, 573292817, 1164186217,
];
/**
* Tells whether the container contains any elements.
*
* Returns: Whether the container is empty.
*/
@property bool empty() const
{
return length == 0;
}
///
@nogc nothrow pure @safe unittest
{
Set!int set;
assert(set.empty);
set.insert(5);
assert(!set.empty);
}
/**
* Removes all elements.
*/
void clear()
{
this.data.clear();
}
///
@nogc nothrow pure @safe unittest
{
Set!int set;
set.insert(5);
assert(!set.empty);
set.clear();
assert(set.empty);
}
/**
* Returns current bucket count in the container.
*
* Bucket count equals to the number of the elements can be saved in the
* container in the best case scenario for key distribution, i.d. every key
* has a unique hash value. In a worse case the bucket count can be less
* than the number of elements stored in the container.
*
* Returns: Current bucket count.
*
* See_Also: $(D_PSYMBOL rehash).
*/
@property size_t bucketCount() const
{
return this.data.bucketCount;
}
/// The maximum number of buckets the container can have.
enum size_t maxBucketCount = primes[$ - 1];
static private size_t calculateHash(U)(ref const U value)
if (is(U == Unqual!T))
{
static if (isIntegral!T || isSomeChar!T || is(T == bool))
{
return (cast(size_t) value);
}
else
{
static assert(false);
}
}
static private size_t locateBucket(ref const DataType buckets,
const size_t hash)
in
{
assert(buckets.length > 0);
}
body
{
return hash % buckets.length;
}
/*
* Returns bucket position for `hash`. `0` may mean the 0th position or an
* empty `buckets` array.
*/
private size_t locateBucket(const size_t hash) const
{
return this.data.length == 0 ? 0 : locateBucket(this.data, hash);
}
private enum InsertStatus : byte
{
found = -1,
failed = 0,
added = 1,
}
/*
* Inserts the value in an empty or deleted bucket. If the value is
* already in there, does nothing and returns InsertStatus.found. If the
* hash array is full returns InsertStatus.failed. Otherwise,
* InsertStatus.added is returned.
*/
private InsertStatus insertInUnusedBucket(ref T value)
{
auto bucketPosition = locateBucket(this.data, calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e == value) // Already in the set.
{
return InsertStatus.found;
}
else if (e.status != BucketStatus.used) // Insert the value.
{
e.content = value;
return InsertStatus.added;
}
}
return InsertStatus.failed;
}
/**
* Inserts a new element.
*
@ -423,31 +463,31 @@ struct Set(T)
* value = Element value.
*
* Returns: Amount of new elements inserted.
*
* Throws: $(D_PSYMBOL HashContainerFullException) if the insertion failed.
*/
size_t insert(T value)
size_t insert()(ref T value)
{
if (this.data.length == 0)
auto e = ((ref v) @trusted => &this.data.insert(v))(value);
if (e.status != BucketStatus.used)
{
this.data = DataType(primes[0], allocator);
e.moveKey(value);
return 1;
}
return 0;
}
InsertStatus status = insertInUnusedBucket(value);
for (; !status; status = insertInUnusedBucket(value))
size_t insert()(T value)
{
if ((this.primes.length - 1) == this.lengthIndex)
auto e = ((ref v) @trusted => &this.data.insert(v))(value);
if (e.status != BucketStatus.used)
{
throw make!HashContainerFullException(defaultAllocator,
"Set is full");
e.key = value;
return 1;
}
rehashToSize(this.lengthIndex + 1);
}
return status == InsertStatus.added;
return 0;
}
///
unittest
@nogc nothrow pure @safe unittest
{
Set!int set;
assert(8 !in set);
@ -464,6 +504,40 @@ struct Set(T)
assert(set.insert(8) == 1);
}
/**
* Inserts the value from a forward range into the set.
*
* Params:
* R = Range type.
* range = Forward range.
*
* Returns: The number of new elements inserted.
*/
size_t insert(R)(scope R range)
if (isForwardRange!R
&& isImplicitlyConvertible!(ElementType!R, T)
&& !isInfinite!R)
{
size_t count;
foreach (e; range)
{
count += insert(e);
}
return count;
}
///
@nogc nothrow pure @safe unittest
{
Set!int set;
int[3] range = [2, 1, 2];
assert(set.insert(range[]) == 2);
assert(1 in set);
assert(2 in set);
}
/**
* Removes an element.
*
@ -475,31 +549,16 @@ struct Set(T)
*/
size_t remove(T value)
{
auto bucketPosition = locateBucket(calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e == value) // Found.
{
e.remove();
return 1;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return 0;
return this.data.remove(value);
}
///
@nogc unittest
@nogc nothrow pure @safe unittest
{
Set!int set;
assert(8 !in set);
set.insert(8);
assert(8 in set);
assert(8 in set);
assert(set.remove(8) == 1);
assert(set.remove(8) == 0);
assert(8 !in set);
@ -509,30 +568,20 @@ struct Set(T)
* $(D_KEYWORD in) operator.
*
* Params:
* U = Type comparable with the element type, used for the lookup.
* value = Element to be searched for.
*
* Returns: $(D_KEYWORD true) if the given element exists in the container,
* $(D_KEYWORD false) otherwise.
*/
bool opBinaryRight(string op : "in")(auto ref const T value) const
bool opBinaryRight(string op : "in", U)(auto ref const U value) const
if (ifTestable!(U, a => T.init == a))
{
auto bucketPosition = locateBucket(calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e == value) // Found.
{
return true;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return false;
return value in this.data;
}
///
@nogc unittest
@nogc nothrow pure @safe unittest
{
Set!int set;
@ -546,18 +595,15 @@ struct Set(T)
* Sets the number of buckets in the container to at least $(D_PARAM n)
* and rearranges all the elements according to their hash values.
*
* If $(D_PARAM n) is greater than the current $(D_PSYMBOL capacity)
* If $(D_PARAM n) is greater than the current $(D_PSYMBOL bucketCount)
* and lower than or equal to $(D_PSYMBOL maxBucketCount), a rehash is
* forced.
*
* If $(D_PARAM n) is greater than $(D_PSYMBOL maxBucketCount),
* $(D_PSYMBOL maxBucketCount) is used instead as a new number of buckets.
*
* If $(D_PARAM n) is equal to the current $(D_PSYMBOL capacity), rehashing
* is forced without resizing the container.
*
* If $(D_PARAM n) is lower than the current $(D_PSYMBOL capacity), the
* function may have no effect.
* If $(D_PARAM n) is less than or equal to the current
* $(D_PSYMBOL bucketCount), the function may have no effect.
*
* Rehashing is automatically performed whenever the container needs space
* to insert new elements.
@ -565,62 +611,29 @@ struct Set(T)
* Params:
* n = Minimum number of buckets.
*/
void rehash(const size_t n)
void rehash(size_t n)
{
size_t lengthIndex;
for (; lengthIndex < primes.length; ++lengthIndex)
{
if (primes[lengthIndex] >= n)
{
break;
}
}
rehashToSize(lengthIndex);
}
// Takes an index in the primes array.
private void rehashToSize(const size_t n)
{
auto storage = DataType(primes[n], allocator);
DataLoop: foreach (ref e1; this.data[])
{
if (e1.status == BucketStatus.used)
{
auto bucketPosition = locateBucket(storage,
calculateHash(e1.content));
foreach (ref e2; storage[bucketPosition .. $])
{
if (e2.status != BucketStatus.used) // Insert the value.
{
e2.content = e1.content;
continue DataLoop;
}
}
return; // Rehashing failed.
}
}
move(storage, this.data);
this.lengthIndex = n;
this.data.rehash(n);
}
/**
* Returns: A bidirectional range that iterates over the $(D_PSYMBOL Set)'s
* elements.
* Returns a bidirectional range over the container.
*
* Returns: A bidirectional range that iterates over the container.
*/
Range opIndex()
{
return typeof(return)(this.data[]);
return typeof(return)(this.data.array[]);
}
/// Ditto.
/// ditto
ConstRange opIndex() const
{
return typeof(return)(this.data[]);
return typeof(return)(this.data.array[]);
}
///
@nogc unittest
@nogc nothrow pure @safe unittest
{
Set!int set;
assert(set[].empty);
@ -629,90 +642,5 @@ struct Set(T)
assert(!set[].empty);
assert(set[].front == 8);
assert(set[].back == 8);
set.remove(8);
assert(set[].empty);
}
private @nogc unittest
{
const Set!int set;
assert(set[].empty);
}
private @nogc unittest
{
Set!int set;
set.insert(8);
auto r1 = set[];
auto r2 = r1.save();
r1.popFront();
assert(r1.empty);
r2.popBack();
assert(r2.empty);
}
private alias DataType = Array!(Bucket!T);
private DataType data;
private size_t lengthIndex;
}
// Basic insertion logic.
private @nogc unittest
{
Set!int set;
assert(set.insert(5) == 1);
assert(set.data[0].status == BucketStatus.empty);
assert(set.data[1].status == BucketStatus.empty);
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
assert(set.data.length == 3);
assert(set.insert(5) == 0);
assert(set.data[0].status == BucketStatus.empty);
assert(set.data[1].status == BucketStatus.empty);
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
assert(set.data.length == 3);
assert(set.insert(9) == 1);
assert(set.data[0].content == 9 && set.data[0].status == BucketStatus.used);
assert(set.data[1].status == BucketStatus.empty);
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
assert(set.data.length == 3);
assert(set.insert(7) == 1);
assert(set.insert(8) == 1);
assert(set.data[0].content == 7);
assert(set.data[1].content == 8);
assert(set.data[2].content == 9);
assert(set.data[3].status == BucketStatus.empty);
assert(set.data[5].content == 5);
assert(set.data.length == 7);
assert(set.insert(16) == 1);
assert(set.data[2].content == 9);
assert(set.data[3].content == 16);
assert(set.data[4].status == BucketStatus.empty);
}
// Static checks.
private unittest
{
import std.range.primitives;
static assert(isBidirectionalRange!(Set!int.ConstRange));
static assert(isBidirectionalRange!(Set!int.Range));
static assert(!isInfinite!(Set!int.Range));
static assert(!hasLength!(Set!int.Range));
static assert(is(Set!uint));
static assert(is(Set!long));
static assert(is(Set!ulong));
static assert(is(Set!short));
static assert(is(Set!ushort));
static assert(is(Set!bool));
}

File diff suppressed because it is too large Load Diff

View File

@ -5,18 +5,21 @@
/**
* This module provides functions for converting between different types.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/format/conv.d,
* tanya/format/conv.d)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/conv.d,
* tanya/conv.d)
*/
module tanya.format.conv;
module tanya.conv;
import std.traits;
import std.traits : Unsigned;
import tanya.container.string;
import tanya.memory;
import tanya.memory.allocator;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range;
/**
* Thrown if a type conversion fails.
@ -39,6 +42,100 @@ final class ConvException : Exception
}
}
/*
* Converts a string $(D_PARAM range) into an integral value of type
* $(D_PARAM T) in $(D_PARAM base).
*
* The convertion stops when $(D_PARAM range) is empty of if the next character
* cannot be converted because it is not a digit (with respect to the
* $(D_PARAM base)) or if the reading the next character would cause integer
* overflow. The function returns the value converted so far then. The front
* element of the $(D_PARAM range) points to the first character cannot be
* converted or $(D_PARAM range) is empty if the whole string could be
* converted.
*
* Base must be between 2 and 36 inclursive. Default base is 10.
*
* The function doesn't handle the sign (+ or -) or number prefixes (like 0x).
*/
package T readIntegral(T, R)(ref R range, const ubyte base = 10)
if (isInputRange!R
&& isSomeChar!(ElementType!R)
&& isIntegral!T
&& isUnsigned!T)
in
{
assert(base >= 2);
assert(base <= 36);
}
do
{
T boundary = cast(T) (T.max / base);
if (range.empty)
{
return T.init;
}
T n;
int digit;
do
{
if (range.front >= 'a')
{
digit = range.front - 'W';
}
else if (range.front >= 'A' && range.front <= 'Z')
{
digit = range.front - '7';
}
else if (range.front >= '0' && range.front <= '9')
{
digit = range.front - '0';
}
else
{
return n;
}
if (digit >= base)
{
return n;
}
n = cast(T) (n * base + digit);
range.popFront();
if (range.empty)
{
return n;
}
}
while (n < boundary);
if (range.front >= 'a')
{
digit = range.front - 'W';
}
else if (range.front >= 'A')
{
digit = range.front - '7';
}
else if (range.front >= '0')
{
digit = range.front - '0';
}
else
{
return n;
}
if (n > cast(T) ((T.max - digit) / base))
{
return n;
}
n = cast(T) (n * base + digit);
range.popFront();
return n;
}
/**
* If the source type $(D_PARAM From) and the target type $(D_PARAM To) are
* equal, does nothing. If $(D_PARAM From) can be implicitly converted to
@ -62,7 +159,7 @@ template to(To)
return from;
}
/// Ditto.
/// ditto
To to(From)(From from)
if (is(Unqual!To == Unqual!From) || (isNumeric!From && isFloatingPoint!To))
{
@ -71,19 +168,13 @@ template to(To)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
auto val = 5.to!int();
assert(val == 5);
static assert(is(typeof(val) == int));
}
private pure nothrow @safe @nogc unittest
{
int val = 5;
assert(val.to!int() == 5);
}
/**
* Performs checked conversion from an integral type $(D_PARAM From) to an
* integral type $(D_PARAM To).
@ -146,133 +237,87 @@ if (isIntegral!From
}
}
private pure nothrow @safe @nogc unittest
/**
* Converts a floating point number to an integral type.
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: Truncated $(D_PARAM from) (everything after the decimal point is
* dropped).
*
* Throws: $(D_PSYMBOL ConvException) if
* $(D_INLINECODE from < To.min || from > To.max).
*/
To to(To, From)(From from)
if (isFloatingPoint!From
&& isIntegral!To
&& !is(Unqual!To == Unqual!From)
&& !is(To == enum))
{
// ubyte -> ushort
assert((cast(ubyte) 0).to!ushort == 0);
assert((cast(ubyte) 1).to!ushort == 1);
assert((cast(ubyte) (ubyte.max - 1)).to!ushort == ubyte.max - 1);
assert((cast(ubyte) ubyte.max).to!ushort == ubyte.max);
// ubyte -> short
assert((cast(ubyte) 0).to!short == 0);
assert((cast(ubyte) 1).to!short == 1);
assert((cast(ubyte) (ubyte.max - 1)).to!short == ubyte.max - 1);
assert((cast(ubyte) ubyte.max).to!short == ubyte.max);
if (from > To.max)
{
throw make!ConvException(defaultAllocator,
"Positive number overflow");
}
else if (from < To.min)
{
throw make!ConvException(defaultAllocator,
"Negative number overflow");
}
return cast(To) from;
}
private unittest
///
@nogc pure @safe unittest
{
// ubyte <- ushort
assert((cast(ushort) 0).to!ubyte == 0);
assert((cast(ushort) 1).to!ubyte == 1);
assert((cast(ushort) (ubyte.max - 1)).to!ubyte == ubyte.max - 1);
assert((cast(ushort) ubyte.max).to!ubyte == ubyte.max);
// ubyte <- short
assert((cast(short) 0).to!ubyte == 0);
assert((cast(short) 1).to!ubyte == 1);
assert((cast(short) (ubyte.max - 1)).to!ubyte == ubyte.max - 1);
assert((cast(short) ubyte.max).to!ubyte == ubyte.max);
// short <-> int
assert(short.min.to!int == short.min);
assert((short.min + 1).to!int == short.min + 1);
assert((cast(short) -1).to!int == -1);
assert((cast(short) 0).to!int == 0);
assert((cast(short) 1).to!int == 1);
assert((short.max - 1).to!int == short.max - 1);
assert(short.max.to!int == short.max);
assert((cast(int) short.min).to!short == short.min);
assert((cast(int) short.min + 1).to!short == short.min + 1);
assert((cast(int) -1).to!short == -1);
assert((cast(int) 0).to!short == 0);
assert((cast(int) 1).to!short == 1);
assert((cast(int) short.max - 1).to!short == short.max - 1);
assert((cast(int) short.max).to!short == short.max);
// uint <-> int
assert((cast(uint) 0).to!int == 0);
assert((cast(uint) 1).to!int == 1);
assert((cast(uint) (int.max - 1)).to!int == int.max - 1);
assert((cast(uint) int.max).to!int == int.max);
assert((cast(int) 0).to!uint == 0);
assert((cast(int) 1).to!uint == 1);
assert((cast(int) (int.max - 1)).to!uint == int.max - 1);
assert((cast(int) int.max).to!uint == int.max);
assert(1.5.to!int == 1);
assert(2147483646.5.to!int == 2147483646);
assert((-2147483647.5).to!int == -2147483647);
assert(2147483646.5.to!uint == 2147483646);
}
private unittest
/**
* Performs checked conversion from an integral type $(D_PARAM From) to an
* $(D_KEYWORD enum).
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_KEYWORD enum) value.
*
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) is not a member of
* $(D_PSYMBOL To).
*/
To to(To, From)(From from)
if (isIntegral!From && is(To == enum))
{
ConvException exception;
try
foreach (m; EnumMembers!To)
{
assert(int.min.to!short == int.min);
if (from == m)
{
return m;
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
throw make!ConvException(defaultAllocator,
"Value not found in enum '" ~ To.stringof ~ "'");
}
private unittest
{
ConvException exception;
try
{
assert(int.max.to!short == int.max);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private unittest
{
ConvException exception;
try
{
assert(uint.max.to!ushort == ushort.max);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private unittest
{
ConvException exception;
try
{
assert((-1).to!uint == -1);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
///
@nogc pure @safe unittest
{
enum Test : int
{
one,
two,
}
assert(Test.one.to!int == 0);
assert(Test.two.to!int == 1);
static assert(is(typeof(1.to!Test) == Test));
assert(0.to!Test == Test.one);
assert(1.to!Test == Test.two);
}
/**
@ -315,7 +360,7 @@ if (isNumeric!From && is(Unqual!To == bool) && !is(Unqual!To == Unqual!From))
}
///
@nogc unittest
@nogc pure @safe unittest
{
assert(!0.0.to!bool);
assert(0.2.to!bool);
@ -326,37 +371,7 @@ if (isNumeric!From && is(Unqual!To == bool) && !is(Unqual!To == Unqual!From))
assert(1.to!bool);
}
private @nogc unittest
{
ConvException exception;
try
{
assert((-1).to!bool);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
ConvException exception;
try
{
assert(2.to!bool);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
/// Ditto.
/// ditto
To to(To, From)(auto ref const From from)
if ((is(From == String) || isSomeString!From) && is(Unqual!To == bool))
{
@ -373,7 +388,7 @@ if ((is(From == String) || isSomeString!From) && is(Unqual!To == bool))
}
///
@nogc unittest
@nogc pure @safe unittest
{
assert("true".to!bool);
assert(!"false".to!bool);
@ -382,21 +397,6 @@ if ((is(From == String) || isSomeString!From) && is(Unqual!To == bool))
}
private @nogc unittest
{
ConvException exception;
try
{
assert("1".to!bool);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
/**
* Converts a boolean to $(D_PARAM To).
*
@ -413,14 +413,14 @@ private @nogc unittest
*
* Returns: $(D_PARAM from) converted to $(D_PARAM To).
*/
To to(To, From)(const From from)
To to(To, From)(From from)
if (is(Unqual!From == bool) && isNumeric!To && !is(Unqual!To == Unqual!From))
{
return from;
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(true.to!float == 1.0);
assert(true.to!double == 1.0);
@ -441,270 +441,127 @@ pure nothrow @safe @nogc unittest
assert(false.to!int == 0);
}
/// Ditto.
To to(To, From)(const From from)
if (is(Unqual!From == bool) && is(Unqual!To == String))
{
return String(from ? "true" : "false");
}
///
@nogc unittest
{
assert(true.to!String == "true");
assert(false.to!String == "false");
}
private @nogc unittest
{
static assert(is(typeof((const String("true")).to!bool)));
static assert(is(typeof(false.to!(const String) == "false")));
}
/**
* Converts a floating point number to an integral type.
* Converts a stringish range to an integral value.
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: Truncated $(D_PARAM from) (everything after the decimal point is
* dropped).
* Returns: $(D_PARAM from) converted to $(D_PARAM To).
*
* Throws: $(D_PSYMBOL ConvException) if
* $(D_INLINECODE from < To.min || from > To.max).
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) doesn't contain an
* integral value.
*/
To to(To, From)(From from)
if (isFloatingPoint!From
&& isIntegral!To
&& !is(Unqual!To == Unqual!From)
&& !is(To == enum))
To to(To, From)(auto ref From from)
if (isInputRange!From && isSomeChar!(ElementType!From) && isIntegral!To)
{
if (from > To.max)
if (from.empty)
{
throw make!ConvException(defaultAllocator, "Input range is empty");
}
static if (isSigned!To)
{
bool negative;
}
if (from.front == '-')
{
static if (isUnsigned!To)
{
throw make!ConvException(defaultAllocator,
"Positive number overflow");
}
else if (from < To.min)
{
throw make!ConvException(defaultAllocator,
"Negative number overflow");
}
return cast(To) from;
}
///
@nogc unittest
{
assert(1.5.to!int == 1);
assert(2147483646.5.to!int == 2147483646);
assert((-2147483647.5).to!int == -2147483647);
assert(2147483646.5.to!uint == 2147483646);
}
private @nogc unittest
{
ConvException exception;
try
{
assert(2147483647.5.to!int == 2147483647);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
ConvException exception;
try
{
assert((-2147483648.5).to!int == -2147483648);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
ConvException exception;
try
{
assert((-21474.5).to!uint == -21474);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
/**
* Performs checked conversion from an integral type $(D_PARAM From) to an
* $(D_KEYWORD enum).
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_KEYWORD enum) value.
*
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) is not a member of
* $(D_PSYMBOL To).
*/
To to(To, From)(From from)
if (isIntegral!From && is(To == enum))
{
foreach (m; EnumMembers!To)
{
if (from == m)
{
return m;
}
}
throw make!ConvException(defaultAllocator,
"Value not found in enum '" ~ To.stringof ~ "'");
}
///
@nogc unittest
{
enum Test : int
{
one,
two,
}
static assert(is(typeof(1.to!Test) == Test));
assert(0.to!Test == Test.one);
assert(1.to!Test == Test.two);
}
private @nogc unittest
{
enum Test : uint
{
one,
two,
}
ConvException exception;
try
{
assert(5.to!Test == Test.one);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
package char[] number2String(T)(const T number, char[] buffer)
{
// abs the integer.
ulong n64 = number < 0 ? -cast(long) number : number;
char[20] rightAligned;
char* start = rightAligned.ptr + rightAligned.sizeof;
while (true)
{
// Do in 32-bit chunks (avoid lots of 64-bit divides even with constant
// denominators).
char* o = start - 8;
uint n;
if (n64 >= 100000000)
{
n = n64 % 100000000;
n64 /= 100000000;
"Negative integer overflow");
}
else
{
n = cast(uint) n64;
n64 = 0;
}
while (n)
{
*--start = cast(char) (n % 10) + '0';
n /= 10;
}
// Ignore the leading zero if it was the last part of the integer.
if (n64 == 0)
{
if ((start[0] == '0')
&& (start != (rightAligned.ptr + rightAligned.sizeof)))
{
++start;
}
break;
}
// Copy leading zeros if it wasn't the most significant part of the
// integer.
while (start != o)
{
*--start = '0';
negative = true;
from.popFront();
}
}
// Get the length that we copied.
auto l = cast(uint) ((rightAligned.ptr + rightAligned.sizeof) - start);
if (l == 0)
if (from.empty)
{
*--start = '0';
l = 1;
throw make!ConvException(defaultAllocator, "Input range is empty");
}
// Write the string.
char* bp = buffer.ptr;
// Set sign.
if (number < 0)
ubyte base = 10;
if (from.front == '0')
{
*bp++ = '-';
from.popFront();
if (from.empty)
{
return To.init;
}
else if (from.front == 'x' || from.front == 'X')
{
base = 16;
from.popFront();
}
else if (from.front == 'b' || from.front == 'B')
{
base = 2;
from.popFront();
}
else
{
base = 8;
}
}
// Copy the string into the target buffer.
uint n = l;
while (n)
auto unsigned = readIntegral!(Unsigned!To, From)(from, base);
if (!from.empty)
{
int i = n;
n -= i;
while (i >= 4)
{
*cast(uint*) bp = *cast(uint*) start;
bp += 4;
start += 4;
i -= 4;
}
while (i)
{
*bp++ = *start++;
--i;
}
}
return buffer[0 .. bp - buffer.ptr];
throw make!ConvException(defaultAllocator, "Integer overflow");
}
private @nogc unittest
static if (isSigned!To)
{
char[21] buf;
assert(number2String(80, buf) == "80");
assert(number2String(-80, buf) == "-80");
assert(number2String(0, buf) == "0");
assert(number2String(uint.max, buf) == "4294967295");
assert(number2String(int.min, buf) == "-2147483648");
if (negative)
{
auto predecessor = cast(Unsigned!To) (unsigned - 1);
if (predecessor > cast(Unsigned!To) To.max)
{
throw make!ConvException(defaultAllocator,
"Negative integer overflow");
}
return cast(To) (-(cast(Largest!(To, ptrdiff_t)) predecessor) - 1);
}
else if (unsigned > cast(Unsigned!To) To.max)
{
throw make!ConvException(defaultAllocator, "Integer overflow");
}
else
{
return unsigned;
}
}
else
{
return unsigned;
}
}
///
@nogc pure @safe unittest
{
assert("1234".to!uint() == 1234);
assert("1234".to!int() == 1234);
assert("1234".to!int() == 1234);
assert("0".to!int() == 0);
assert("-0".to!int() == 0);
assert("0x10".to!int() == 16);
assert("0X10".to!int() == 16);
assert("-0x10".to!int() == -16);
assert("0b10".to!int() == 2);
assert("0B10".to!int() == 2);
assert("-0b10".to!int() == -2);
assert("010".to!int() == 8);
assert("-010".to!int() == -8);
assert("-128".to!byte == cast(byte) -128);
}

2460
source/tanya/format.d Normal file

File diff suppressed because it is too large Load Diff

340
source/tanya/hash/lookup.d Normal file
View File

@ -0,0 +1,340 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Non-cryptographic, lookup hash functions.
*
* Copyright: Eugene Wissner 2018-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/hash/lookup.d,
* tanya/hash/lookup.d)
*/
module tanya.hash.lookup;
import tanya.meta.trait;
import tanya.range.primitive;
private struct Hasher
{
static if (size_t.sizeof == 4)
{
enum uint offsetBasis = 2166136261;
enum uint prime = 16777619;
}
else static if (size_t.sizeof == 8)
{
enum ulong offsetBasis = 14695981039346656037UL;
enum ulong prime = 1099511628211UL;
}
else static if (size_t.sizeof == 16)
{
enum size_t offsetBasis = (size_t(0x6c62272e07bb0142UL) << 64) + 0x62b821756295c58dUL;
enum size_t prime = (size_t(1) << 88) + (1 << 8) + 0x3b;
}
else
{
static assert(false, "FNV requires at least 32-bit hash length");
}
size_t hash = offsetBasis;
void opCall(T)(auto ref T key)
{
static if (is(typeof(key.toHash()) == size_t))
{
opCall(key.toHash()); // Combine user-defined hashes
}
else static if (isScalarType!T || isPointer!T)
{
// Treat as an array of words
static if (T.sizeof % size_t.sizeof == 0
&& T.alignof >= size_t.alignof)
alias CastT = size_t;
// (64-bit or 128-bit) Treat as an array of ints
else static if (T.sizeof % uint.sizeof == 0
&& T.alignof >= uint.alignof)
alias CastT = uint;
// Treat as an array of bytes
else
alias CastT = ubyte;
add((() @trusted => (cast(const CastT*) &key)[0 .. T.sizeof / CastT.sizeof])());
}
else static if (isArray!T && isScalarType!(ElementType!T))
{
// Treat as an array of words
static if (ElementType!T.sizeof % size_t.sizeof == 0
&& ElementType!T.alignof >= size_t.alignof)
alias CastT = size_t;
// (64-bit or 128-bit) Treat as an array of ints
else static if (ElementType!T.sizeof % uint.sizeof == 0
&& ElementType!T.alignof >= uint.alignof)
alias CastT = uint;
// Treat as an array of bytes
else
alias CastT = ubyte;
add(cast(const CastT[]) key);
}
else static if (is(T == typeof(null)))
{
add(key);
}
else static if (isInputRange!T && !isInfinite!T)
{
foreach (e; key)
{
opCall(e);
}
}
else
{
static assert(false, "Hash function is not available");
}
}
void add(scope const ubyte[] key) @nogc nothrow pure @safe
{
// FNV-1a
foreach (c; key)
{
this.hash = (this.hash ^ c) * prime;
}
}
void add(scope const size_t[] key) @nogc nothrow pure @safe
{
static if (size_t.sizeof == 4)
{
// Partial MurmurHash3_x86_32 (no finalization)
enum uint c1 = 0xcc9e2d51;
enum uint c2 = 0x1b873593;
alias h1 = hash;
foreach (x; key)
{
auto k1 = x * c1;
k1 = (k1 << 15) | (k1 >> (32 - 15));
k1 *= c2;
h1 ^= k1;
h1 = (h1 << 13) | (h1 >> (32 - 13));
h1 = h1 * 5 + 0xe6546b64;
}
}
else static if (size_t.sizeof == 8)
{
// Partial 64-bit MurmurHash64A (no finalization)
alias h = hash;
enum ulong m = 0xc6a4a7935bd1e995UL;
foreach (x; key)
{
auto k = x * m;
k ^= k >>> 47;
k *= m;
h ^= k;
h *= m;
}
}
else static if (size_t.sizeof == 16)
{
// Partial MurmurHash3_x64_128 (no finalization)
// treating each size_t as a pair of ulong.
ulong h1 = cast(ulong) hash;
ulong h2 = cast(ulong) (hash >> 64);
enum ulong c1 = 0x87c37b91114253d5UL;
enum ulong c2 = 0x4cf5ad432745937fUL;
foreach (x; key)
{
auto k1 = cast(ulong) x;
auto k2 = cast(ulong) (x >> 64);
k1 *= c1; k1 = (k1 << 32) | (k1 >> (64 - 31)); k1 *= c2; h1 ^= k1;
h1 = (h1 << 27) | (h1 >> (64 - 27)); h1 += h2; h1 = h1*5+0x52dce729;
k2 *= c2; k2 = (k2 << 33) | (k2 >> (64 - 33)); k2 *= c1; h2 ^= k2;
h2 = (h2 << 31) | (h2 >> (64 - 31)); h2 += h1; h2 = h2*5+0x38495ab5;
}
hash = cast(size_t) h1 + ((cast(size_t) h2) << 64);
}
else
{
static assert(0, "Hash length must be either 32, 64, or 128 bits.");
}
}
static if (size_t.sizeof != uint.sizeof)
void add(scope const uint[] key) @nogc nothrow pure @trusted
{
static if (size_t.sizeof == 8)
{
// Partial 32-bit MurmurHash64B (no finalization)
enum uint m = 0x5bd1e995;
enum r = 24;
uint h1 = cast(uint) hash;
uint h2 = cast(uint) (hash >> 32);
const(uint)* data = key.ptr;
auto len = key.length;
for (; len >= 2; data += 2, len -= 2)
{
uint k1 = data[0];
k1 *= m; k1 ^= k1 >> r; k1 *= m;
h1 *= m; h1 ^= k1;
uint k2 = data[1];
k2 *= m; k2 ^= k2 >> r; k2 *= m;
h2 *= m; h2 ^= k2;
}
if (len)
{
uint k1 = data[0];
k1 *= m; k1 ^= k1 >> r; k1 *= m;
h1 *= m; h1 ^= k1;
}
hash = cast(ulong) h1 + ((cast(ulong) h2) << 32);
}
else static if (size_t.sizeof == 16)
{
// Partial MurmurHash3_x86_128 (no finalization)
enum uint c1 = 0x239b961b;
enum uint c2 = 0xab0e9789;
enum uint c3 = 0x38b34ae5;
enum uint c4 = 0xa1e38b93;
uint h1 = cast(uint) hash;
uint h2 = cast(uint) (hash >> 32);
uint h3 = cast(uint) (hash >> 64);
uint h4 = cast(uint) (hash >> 96);
const(uint)* data = key.ptr;
auto len = key.length;
for (; len >= 4; data += 4, len -= 4)
{
uint k1 = data[0];
uint k2 = data[1];
uint k3 = data[2];
uint k4 = data[3];
h1 = (h1 << 19) | (h1 >> (32 - 19)); h1 += h2; h1 = h1*5+0x561ccd1b;
k2 *= c2; k2 = (k2 << 16) | (k2 >> (32 - 16)); k2 *= c3; h2 ^= k2;
h2 = (h2 << 17) | (h2 >> (32 - 17)); h2 += h3; h2 = h2*5+0x0bcaa747;
k3 *= c3; k3 = (k3 << 17) | (k3 >> (32 - 17)); k3 *= c4; h3 ^= k3;
h3 = (h3 << 15) | (h3 >> (32 - 15)); h3 += h4; h3 = h3*5+0x96cd1c35;
k4 *= c4; k4 = (k4 << 18) | (k4 >> (32 - 18)); k4 *= c1; h4 ^= k4;
h4 = (h4 << 13) | (h4 >> (32 - 13)); h4 += h1; h4 = h4*5+0x32ac3b17;
}
uint k1, k2, k3;
switch (len) // 0, 1, 2, 3
{
case 3:
k3 = data[2];
k3 *= c3; k3 = (k3 << 17) | (k3 >> (32 - 17)); k3 *= c4; h3 ^= k3;
goto case;
case 2:
k2 = data[1];
k2 *= c2; k2 = (k2 << 16) | (k2 >> (32 - 16)); k2 *= c3; h2 ^= k2;
goto case;
case 1:
k1 = data[0];
k1 *= c1; k1 = (k1 << 15) | (k1 >> (32 - 15)); k1 *= c2; h1 ^= k1;
break;
}
hash = cast(size_t) h1 +
((cast(size_t) h2) << 32) +
((cast(size_t) h3) << 64) +
((cast(size_t) h4) << 96);
}
else
{
static assert(0, "Hash length must be either 32, 64, or 128 bits.");
}
}
}
/**
* Takes an argument of an arbitrary type $(D_PARAM T) and calculates the hash
* value.
*
* Hash calculation is supported for all scalar types. Aggregate types, like
* $(D_KEYWORD struct)s, should implement `toHash`-function:
* ---
* size_t toHash() const
* {
* return hash;
* }
* ---
*
* For pointers and for scalar types implicitly convertible to `size_t` this
* is an identity operation (i.e. the value is cast to `size_t` and returned
* unaltered). Integer types wider than `size_t` are XOR folded down to
* `size_t`. Other scalar types use an architecture-dependent hash function
* based on their width and alignment.
* If the type provides a `toHash`-function, only `toHash()` is called and its
* result is returned.
*
* This function also accepts input ranges that contain hashable elements.
* Individual values are combined then and the resulting hash is returned.
*
* Params:
* T = Hashable type.
* key = Hashable value.
*
* Returns: Calculated hash value.
*
* See_Also: $(LINK http://www.isthe.com/chongo/tech/comp/fnv/).
*/
size_t hash(T)(auto ref T key)
{
static if (is(typeof(key.toHash()) == size_t))
{
return key.toHash();
}
else static if ((isIntegral!T || isSomeChar!T || isBoolean!T)
&& T.sizeof <= size_t.sizeof)
{
return cast(size_t) key;
}
else static if (isIntegral!T && T.sizeof > size_t.sizeof)
{
return cast(size_t) (key ^ (key >>> (size_t.sizeof * 8)));
}
else static if (isPointer!T || is(T : typeof(null)))
{
return (() @trusted => cast(size_t) key)();
}
else
{
Hasher hasher;
hasher(key);
return hasher.hash;
}
}
/**
* Determines whether $(D_PARAM hasher) is hash function for $(D_PARAM T), i.e.
* it is callable with a value of type $(D_PARAM T) and returns a
* $(D_PSYMBOL size_t) value.
*
* Params:
* hasher = Hash function candidate.
* T = Type to test the hash function with.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM hasher) is a hash function for
* $(D_PARAM T), $(D_KEYWORD false) otherwise.
*/
template isHashFunction(alias hasher, T)
{
private alias wrapper = (T x) => hasher(x);
enum bool isHashFunction = is(typeof(wrapper(T.init)) == size_t);
}
///
@nogc nothrow pure @safe unittest
{
static assert(isHashFunction!(hash, int));
}

View File

@ -3,15 +3,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Network programming.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2018-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/network/package.d,
* tanya/network/package.d)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/hash/package.d,
* tanya/hash/package.d)
*/
module tanya.network;
module tanya.hash;
public import tanya.network.socket;
public import tanya.hash.lookup;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,111 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Number theory.
*
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/math/nbtheory.d,
* tanya/math/nbtheory.d)
*/
module tanya.math.nbtheory;
import tanya.meta.trait;
import tanya.meta.transform;
import core.math : fabs;
import std.math : log;
/**
* Calculates the absolute value of a number.
*
* Params:
* T = Argument type.
* x = Argument.
*
* Returns: Absolute value of $(D_PARAM x).
*/
Unqual!T abs(T)(T x)
if (isIntegral!T)
{
static if (isSigned!T)
{
return x >= 0 ? x : -x;
}
else
{
return x;
}
}
///
@nogc nothrow pure @safe unittest
{
int i = -1;
assert(i.abs == 1);
static assert(is(typeof(i.abs) == int));
uint u = 1;
assert(u.abs == 1);
static assert(is(typeof(u.abs) == uint));
}
/// ditto
Unqual!T abs(T)(T x)
if (isFloatingPoint!T)
{
return fabs(x);
}
///
@nogc nothrow pure @safe unittest
{
float f = -1.64;
assert(f.abs == 1.64F);
static assert(is(typeof(f.abs) == float));
double d = -1.64;
assert(d.abs == 1.64);
static assert(is(typeof(d.abs) == double));
real r = -1.64;
assert(r.abs == 1.64L);
static assert(is(typeof(r.abs) == real));
}
/**
* Calculates natural logarithm of $(D_PARAM x).
*
* Params:
* T = Argument type.
* x = Argument.
*
* Returns: Natural logarithm of $(D_PARAM x).
*/
Unqual!T ln(T)(T x)
if (isFloatingPoint!T)
{
return log(x);
}
///
@nogc nothrow pure @safe unittest
{
import tanya.math;
assert(isNaN(ln(-7.389f)));
assert(isNaN(ln(-7.389)));
assert(isNaN(ln(-7.389L)));
assert(isInfinity(ln(0.0f)));
assert(isInfinity(ln(0.0)));
assert(isInfinity(ln(0.0L)));
assert(ln(1.0f) == 0.0f);
assert(ln(1.0) == 0.0);
assert(ln(1.0L) == 0.0L);
}

View File

@ -5,7 +5,14 @@
/**
* This package provides mathematical functions.
*
* Copyright: Eugene Wissner 2016-2017.
* The $(D_PSYMBOL tanya.math) package itself provides only representation
* functions for built-in types, such as functions that provide information
* about internal representation of floating-point numbers and low-level
* operatons on these. Actual mathematical functions and additional types can
* be found in its submodules. $(D_PSYMBOL tanya.math) doesn't import any
* submodules publically, they should be imported explicitly.
*
* Copyright: Eugene Wissner 2016-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
@ -14,21 +21,532 @@
*/
module tanya.math;
import std.traits;
public import tanya.math.mp;
public import tanya.math.random;
import tanya.math.nbtheory;
import tanya.meta.trait;
import tanya.meta.transform;
version (unittest)
/// Floating-point number precisions according to IEEE-754.
enum IEEEPrecision : ubyte
{
import std.algorithm.iteration;
single = 4, /// Single precision: 64-bit.
double_ = 8, /// Single precision: 64-bit.
doubleExtended = 10, /// Double extended precision: 80-bit.
}
/**
* Tests the precision of floating-point type $(D_PARAM F).
*
* For $(D_KEYWORD float) $(D_PSYMBOL ieeePrecision) always evaluates to
* $(D_INLINECODE IEEEPrecision.single); for $(D_KEYWORD double) - to
* $(D_INLINECODE IEEEPrecision.double). It returns different values only
* for $(D_KEYWORD real), since $(D_KEYWORD real) is a platform-dependent type.
*
* If $(D_PARAM F) is a $(D_KEYWORD real) and the target platform isn't
* currently supported, static assertion error will be raised (you can use
* $(D_INLINECODE is(typeof(ieeePrecision!F))) for testing the platform support
* without a compilation error).
*
* Params:
* F = Type to be tested.
*
* Returns: Precision according to IEEE-754.
*
* See_Also: $(D_PSYMBOL IEEEPrecision).
*/
template ieeePrecision(F)
if (isFloatingPoint!F)
{
static if (F.sizeof == float.sizeof)
{
enum IEEEPrecision ieeePrecision = IEEEPrecision.single;
}
else static if (F.sizeof == double.sizeof)
{
enum IEEEPrecision ieeePrecision = IEEEPrecision.double_;
}
else version (X86)
{
enum IEEEPrecision ieeePrecision = IEEEPrecision.doubleExtended;
}
else version (X86_64)
{
enum IEEEPrecision ieeePrecision = IEEEPrecision.doubleExtended;
}
else
{
static assert(false, "Unsupported IEEE 754 floating point precision");
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(ieeePrecision!float == IEEEPrecision.single);
static assert(ieeePrecision!double == IEEEPrecision.double_);
}
package(tanya) union FloatBits(F)
{
Unqual!F floating;
static if (ieeePrecision!F == IEEEPrecision.single)
{
uint integral;
enum uint expMask = 0x7f800000;
}
else static if (ieeePrecision!F == IEEEPrecision.double_)
{
ulong integral;
enum ulong expMask = 0x7ff0000000000000;
}
else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
{
struct // Little-endian.
{
ulong mantissa;
ushort exp;
}
enum ulong mantissaMask = 0x7fffffffffffffff;
enum uint expMask = 0x7fff;
}
else
{
static assert(false, "Unsupported IEEE 754 floating point precision");
}
}
/**
* Floating-point number classifications.
*/
enum FloatingPointClass : ubyte
{
/**
* Not a Number.
*
* See_Also: $(D_PSYMBOL isNaN).
*/
nan,
/// Zero.
zero,
/**
* Infinity.
*
* See_Also: $(D_PSYMBOL isInfinity).
*/
infinite,
/**
* Denormalized number.
*
* See_Also: $(D_PSYMBOL isSubnormal).
*/
subnormal,
/**
* Normalized number.
*
* See_Also: $(D_PSYMBOL isNormal).
*/
normal,
}
/**
* Returns whether $(D_PARAM x) is a NaN, zero, infinity, subnormal or
* normalized number.
*
* This function doesn't distinguish between negative and positive infinity,
* negative and positive NaN or negative and positive zero.
*
* Params:
* F = Type of the floating point number.
* x = Floating point number.
*
* Returns: Classification of $(D_PARAM x).
*/
FloatingPointClass classify(F)(F x)
if (isFloatingPoint!F)
{
if (x == 0)
{
return FloatingPointClass.zero;
}
FloatBits!F bits;
bits.floating = abs(x);
static if (ieeePrecision!F == IEEEPrecision.single)
{
if (bits.integral > bits.expMask)
{
return FloatingPointClass.nan;
}
else if (bits.integral == bits.expMask)
{
return FloatingPointClass.infinite;
}
else if (bits.integral < (1 << 23))
{
return FloatingPointClass.subnormal;
}
}
else static if (ieeePrecision!F == IEEEPrecision.double_)
{
if (bits.integral > bits.expMask)
{
return FloatingPointClass.nan;
}
else if (bits.integral == bits.expMask)
{
return FloatingPointClass.infinite;
}
else if (bits.integral < (1L << 52))
{
return FloatingPointClass.subnormal;
}
}
else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
{
if (bits.exp == bits.expMask)
{
if ((bits.mantissa & bits.mantissaMask) == 0)
{
return FloatingPointClass.infinite;
}
else
{
return FloatingPointClass.nan;
}
}
else if (bits.exp == 0)
{
return FloatingPointClass.subnormal;
}
else if (bits.mantissa < (1L << 63)) // "Unnormal".
{
return FloatingPointClass.nan;
}
}
return FloatingPointClass.normal;
}
///
@nogc nothrow pure @safe unittest
{
assert(classify(0.0) == FloatingPointClass.zero);
assert(classify(double.nan) == FloatingPointClass.nan);
assert(classify(double.infinity) == FloatingPointClass.infinite);
assert(classify(-double.infinity) == FloatingPointClass.infinite);
assert(classify(1.4) == FloatingPointClass.normal);
assert(classify(1.11254e-307 / 10) == FloatingPointClass.subnormal);
assert(classify(0.0f) == FloatingPointClass.zero);
assert(classify(float.nan) == FloatingPointClass.nan);
assert(classify(float.infinity) == FloatingPointClass.infinite);
assert(classify(-float.infinity) == FloatingPointClass.infinite);
assert(classify(0.3) == FloatingPointClass.normal);
assert(classify(5.87747e-38f / 10) == FloatingPointClass.subnormal);
assert(classify(0.0L) == FloatingPointClass.zero);
assert(classify(real.nan) == FloatingPointClass.nan);
assert(classify(real.infinity) == FloatingPointClass.infinite);
assert(classify(-real.infinity) == FloatingPointClass.infinite);
}
/**
* Determines whether $(D_PARAM x) is a finite number.
*
* Params:
* F = Type of the floating point number.
* x = Floating point number.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is a finite number,
* $(D_KEYWORD false) otherwise.
*
* See_Also: $(D_PSYMBOL isInfinity).
*/
bool isFinite(F)(F x)
if (isFloatingPoint!F)
{
FloatBits!F bits;
static if (ieeePrecision!F == IEEEPrecision.single
|| ieeePrecision!F == IEEEPrecision.double_)
{
bits.floating = x;
bits.integral &= bits.expMask;
return bits.integral != bits.expMask;
}
else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
{
bits.floating = abs(x);
return (bits.exp != bits.expMask)
&& (bits.exp == 0 || bits.mantissa >= (1L << 63));
}
}
///
@nogc nothrow pure @safe unittest
{
assert(!isFinite(float.infinity));
assert(!isFinite(-double.infinity));
assert(isFinite(0.0));
assert(!isFinite(float.nan));
assert(isFinite(5.87747e-38f / 10));
assert(isFinite(1.11254e-307 / 10));
assert(isFinite(0.5));
}
/**
* Determines whether $(D_PARAM x) is $(B n)ot $(B a) $(B n)umber (NaN).
*
* Params:
* F = Type of the floating point number.
* x = Floating point number.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is not a number,
* $(D_KEYWORD false) otherwise.
*/
bool isNaN(F)(F x)
if (isFloatingPoint!F)
{
FloatBits!F bits;
bits.floating = abs(x);
static if (ieeePrecision!F == IEEEPrecision.single
|| ieeePrecision!F == IEEEPrecision.double_)
{
return bits.integral > bits.expMask;
}
else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
{
const maskedMantissa = bits.mantissa & bits.mantissaMask;
if ((bits.exp == bits.expMask && maskedMantissa != 0)
|| ((bits.exp != 0) && (bits.mantissa < (1L << 63))))
{
return true;
}
return false;
}
}
///
@nogc nothrow pure @safe unittest
{
assert(isNaN(float.init));
assert(isNaN(double.init));
assert(isNaN(real.init));
}
/**
* Determines whether $(D_PARAM x) is a positive or negative infinity.
*
* Params:
* F = Type of the floating point number.
* x = Floating point number.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is infinity, $(D_KEYWORD false)
* otherwise.
*
* See_Also: $(D_PSYMBOL isFinite).
*/
bool isInfinity(F)(F x)
if (isFloatingPoint!F)
{
FloatBits!F bits;
bits.floating = abs(x);
static if (ieeePrecision!F == IEEEPrecision.single
|| ieeePrecision!F == IEEEPrecision.double_)
{
return bits.integral == bits.expMask;
}
else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
{
return (bits.exp == bits.expMask)
&& ((bits.mantissa & bits.mantissaMask) == 0);
}
}
///
@nogc nothrow pure @safe unittest
{
assert(isInfinity(float.infinity));
assert(isInfinity(-float.infinity));
assert(isInfinity(double.infinity));
assert(isInfinity(-double.infinity));
assert(isInfinity(real.infinity));
assert(isInfinity(-real.infinity));
}
/**
* Determines whether $(D_PARAM x) is a denormilized number or not.
*
* Denormalized number is a number between `0` and `1` that cannot be
* represented as
*
* <pre>
* m*2<sup>e</sup>
* </pre>
*
* where $(I m) is the mantissa and $(I e) is an exponent that fits into the
* exponent field of the type $(D_PARAM F).
*
* `0` is neither normalized nor denormalized.
*
* Params:
* F = Type of the floating point number.
* x = Floating point number.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is a denormilized number,
* $(D_KEYWORD false) otherwise.
*
* See_Also: $(D_PSYMBOL isNormal).
*/
bool isSubnormal(F)(F x)
if (isFloatingPoint!F)
{
FloatBits!F bits;
bits.floating = abs(x);
static if (ieeePrecision!F == IEEEPrecision.single)
{
return bits.integral < (1 << 23) && bits.integral > 0;
}
else static if (ieeePrecision!F == IEEEPrecision.double_)
{
return bits.integral < (1L << 52) && bits.integral > 0;
}
else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
{
return bits.exp == 0 && bits.mantissa != 0;
}
}
///
@nogc nothrow pure @safe unittest
{
assert(!isSubnormal(0.0f));
assert(!isSubnormal(float.nan));
assert(!isSubnormal(float.infinity));
assert(!isSubnormal(0.3f));
assert(isSubnormal(5.87747e-38f / 10));
assert(!isSubnormal(0.0));
assert(!isSubnormal(double.nan));
assert(!isSubnormal(double.infinity));
assert(!isSubnormal(1.4));
assert(isSubnormal(1.11254e-307 / 10));
assert(!isSubnormal(0.0L));
assert(!isSubnormal(real.nan));
assert(!isSubnormal(real.infinity));
}
/**
* Determines whether $(D_PARAM x) is a normilized number or not.
*
* Normalized number is a number that can be represented as
*
* <pre>
* m*2<sup>e</sup>
* </pre>
*
* where $(I m) is the mantissa and $(I e) is an exponent that fits into the
* exponent field of the type $(D_PARAM F).
*
* `0` is neither normalized nor denormalized.
*
* Params:
* F = Type of the floating point number.
* x = Floating point number.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is a normilized number,
* $(D_KEYWORD false) otherwise.
*
* See_Also: $(D_PSYMBOL isSubnormal).
*/
bool isNormal(F)(F x)
if (isFloatingPoint!F)
{
static if (ieeePrecision!F == IEEEPrecision.single
|| ieeePrecision!F == IEEEPrecision.double_)
{
FloatBits!F bits;
bits.floating = x;
bits.integral &= bits.expMask;
return bits.integral != 0 && bits.integral != bits.expMask;
}
else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
{
return classify(x) == FloatingPointClass.normal;
}
}
///
@nogc nothrow pure @safe unittest
{
assert(!isNormal(0.0f));
assert(!isNormal(float.nan));
assert(!isNormal(float.infinity));
assert(isNormal(0.3f));
assert(!isNormal(5.87747e-38f / 10));
assert(!isNormal(0.0));
assert(!isNormal(double.nan));
assert(!isNormal(double.infinity));
assert(isNormal(1.4));
assert(!isNormal(1.11254e-307 / 10));
assert(!isNormal(0.0L));
assert(!isNormal(real.nan));
assert(!isNormal(real.infinity));
}
/**
* Determines whether the sign bit of $(D_PARAM x) is set or not.
*
* If the sign bit, $(D_PARAM x) is a negative number, otherwise positive.
*
* Params:
* F = Type of the floating point number.
* x = Floating point number.
*
* Returns: $(D_KEYWORD true) if the sign bit of $(D_PARAM x) is set,
* $(D_KEYWORD false) otherwise.
*/
bool signBit(F)(F x)
if (isFloatingPoint!F)
{
FloatBits!F bits;
bits.floating = x;
static if (ieeePrecision!F == IEEEPrecision.single)
{
return (bits.integral & (1 << 31)) != 0;
}
else static if (ieeePrecision!F == IEEEPrecision.double_)
{
return (bits.integral & (1L << 63)) != 0;
}
else static if (ieeePrecision!F == IEEEPrecision.doubleExtended)
{
return (bits.exp & (1 << 15)) != 0;
}
}
///
@nogc nothrow pure @safe unittest
{
assert(signBit(-1.0f));
assert(!signBit(1.0f));
assert(signBit(-1.0));
assert(!signBit(1.0));
assert(signBit(-1.0L));
assert(!signBit(1.0L));
}
/**
* Computes $(D_PARAM x) to the power $(D_PARAM y) modulo $(D_PARAM z).
*
* If $(D_PARAM I) is an $(D_PSYMBOL Integer), the allocator of $(D_PARAM x)
* is used to allocate the result.
*
* Params:
* I = Base type.
* G = Exponent type.
@ -46,9 +564,9 @@ H pow(I, G, H)(in auto ref I x, in auto ref G y, in auto ref H z)
if (isIntegral!I && isIntegral!G && isIntegral!H)
in
{
assert(z > 0, "Division by zero.");
assert(z > 0, "Division by zero");
}
body
do
{
G mask = G.max / 2 + 1;
H result;
@ -82,48 +600,8 @@ body
return result;
}
/// Ditto.
I pow(I)(const auto ref I x, const auto ref I y, const auto ref I z)
if (is(I == Integer))
in
{
assert(z.length > 0, "Division by zero.");
}
body
{
size_t i;
auto tmp1 = Integer(x, x.allocator);
auto result = Integer(x.allocator);
bool firstBit;
if (x.size == 0 && y.size != 0)
{
i = y.size;
}
else
{
result = 1;
}
while (i < y.size)
{
for (uint mask = 0x01; mask != 0x10000000; mask <<= 1)
{
if (y.rep[i] & mask)
{
result *= tmp1;
result %= z;
}
auto tmp2 = tmp1;
tmp1 *= tmp2;
tmp1 %= z;
}
++i;
}
return result;
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(pow(3, 5, 7) == 5);
assert(pow(2, 2, 1) == 0);
@ -135,71 +613,3 @@ pure nothrow @safe @nogc unittest
assert(pow(0, 0, 5) == 1);
assert(pow(0, 5, 5) == 0);
}
///
unittest
{
assert(pow(Integer(3), Integer(5), Integer(7)) == 5);
assert(pow(Integer(2), Integer(2), Integer(1)) == 0);
assert(pow(Integer(3), Integer(3), Integer(3)) == 0);
assert(pow(Integer(7), Integer(4), Integer(2)) == 1);
assert(pow(Integer(53), Integer(0), Integer(2)) == 1);
assert(pow(Integer(53), Integer(1), Integer(3)) == 2);
assert(pow(Integer(53), Integer(2), Integer(5)) == 4);
assert(pow(Integer(0), Integer(0), Integer(5)) == 1);
assert(pow(Integer(0), Integer(5), Integer(5)) == 0);
}
/**
* Checks if $(D_PARAM x) is a prime.
*
* Params:
* x = The number should be checked.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is a prime number,
* $(D_KEYWORD false) otherwise.
*/
bool isPseudoprime(ulong x) nothrow pure @safe @nogc
{
return pow(2, x - 1, x) == 1;
}
///
unittest
{
uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719,
74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821,
74827, 9973, 104729, 15485867, 49979693, 104395303,
593441861, 104729, 15485867, 49979693, 104395303,
593441861, 899809363, 982451653];
known.each!((ref x) => assert(isPseudoprime(x)));
}
/**
* Params:
* I = Value type.
* x = Value.
*
* Returns: The absolute value of a number.
*/
I abs(I : Integer)(const auto ref I x)
{
auto result = Integer(x, x.allocator);
result.sign = Sign.positive;
return result;
}
/// Ditto.
I abs(I : Integer)(I x)
{
x.sign = Sign.positive;
return x;
}
/// Ditto.
I abs(I)(const I x)
if (isIntegral!I)
{
return x >= 0 ? x : -x;
}

View File

@ -5,7 +5,7 @@
/**
* Random number generator.
*
* Copyright: Eugene Wissner 2016.
* Copyright: Eugene Wissner 2016-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
@ -14,12 +14,9 @@
*/
module tanya.math.random;
import std.digest.sha;
import std.typecons;
import tanya.memory;
/// Block size of entropy accumulator (SHA-512).
enum blockSize = 64;
import tanya.memory.allocator;
import tanya.typecons;
/// Maximum amount gathered from the entropy sources.
enum maxGather = 128;
@ -39,7 +36,7 @@ class EntropyException : Exception
this(string msg,
string file = __FILE__,
size_t line = __LINE__,
Throwable next = null) pure @safe nothrow const @nogc
Throwable next = null) const @nogc nothrow pure @safe
{
super(msg, file, line, next);
}
@ -56,17 +53,17 @@ abstract class EntropySource
/**
* Returns: Minimum bytes required from the entropy source.
*/
@property ubyte threshold() const pure nothrow @safe @nogc;
@property ubyte threshold() const @nogc nothrow pure @safe;
/**
* Returns: Whether this entropy source is strong.
*/
@property bool strong() const pure nothrow @safe @nogc;
@property bool strong() const @nogc nothrow pure @safe;
/**
* Returns: Amount of already generated entropy.
*/
@property ushort size() const pure nothrow @safe @nogc
@property ushort size() const @nogc nothrow pure @safe
{
return size_;
}
@ -76,7 +73,7 @@ abstract class EntropySource
* size = Amount of already generated entropy. Cannot be smaller than the
* already set value.
*/
@property void size(ushort size) pure nothrow @safe @nogc
@property void size(ushort size) @nogc nothrow pure @safe
{
size_ = size;
}
@ -89,14 +86,39 @@ abstract class EntropySource
* to fill the buffer).
*
* Returns: Number of bytes that were copied to the $(D_PARAM output)
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
* or nothing on error.
*
* Postcondition: Returned length is less than or equal to
* $(D_PARAM output) length.
*/
Nullable!ubyte poll(out ubyte[maxGather] output) @nogc;
}
version (CRuntime_Bionic)
{
version = SecureARC4Random;
}
else version (OSX)
{
version = SecureARC4Random;
}
else version (OpenBSD)
{
version = SecureARC4Random;
}
else version (NetBSD)
{
version = SecureARC4Random;
}
else version (Solaris)
{
version = SecureARC4Random;
}
version (linux)
{
extern (C) long syscall(long number, ...) nothrow @system @nogc;
import core.stdc.config : c_long;
private extern(C) c_long syscall(c_long number, ...) @nogc nothrow @system;
/**
* Uses getrandom system call.
@ -106,7 +128,7 @@ version (linux)
/**
* Returns: Minimum bytes required from the entropy source.
*/
override @property ubyte threshold() const pure nothrow @safe @nogc
override @property ubyte threshold() const @nogc nothrow pure @safe
{
return 32;
}
@ -114,7 +136,7 @@ version (linux)
/**
* Returns: Whether this entropy source is strong.
*/
override @property bool strong() const pure nothrow @safe @nogc
override @property bool strong() const @nogc nothrow pure @safe
{
return true;
}
@ -127,17 +149,18 @@ version (linux)
* to fill the buffer).
*
* Returns: Number of bytes that were copied to the $(D_PARAM output)
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
* or nothing on error.
*/
override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow @nogc
override Nullable!ubyte poll(out ubyte[maxGather] output) @nogc nothrow
out (length)
{
assert(length <= maxGather);
assert(length.isNull || length.get <= maxGather);
}
body
do
{
// int getrandom(void *buf, size_t buflen, unsigned int flags);
auto length = syscall(318, output.ptr, output.length, 0);
import mir.linux._asm.unistd : NR_getrandom;
auto length = syscall(NR_getrandom, output.ptr, output.length, 0);
Nullable!ubyte ret;
if (length >= 0)
@ -147,189 +170,172 @@ version (linux)
return ret;
}
}
version (X86_64)
}
else version (SecureARC4Random)
{
private unittest
{
auto entropy = defaultAllocator.make!Entropy();
ubyte[blockSize] output;
output = entropy.random;
defaultAllocator.dispose(entropy);
}
}
}
private extern(C) void arc4random_buf(scope void* buf, size_t nbytes)
@nogc nothrow @system;
/**
* Pseudorandom number generator.
* ---
* auto entropy = defaultAllocator.make!Entropy();
*
* ubyte[blockSize] output;
*
* output = entropy.random;
*
* defaultAllocator.dispose(entropy);
* ---
* Uses arc4random_buf.
*/
class Entropy
class PlatformEntropySource : EntropySource
{
/// Entropy sources.
protected EntropySource[] sources;
private ubyte sourceCount_;
private shared Allocator allocator;
/// Entropy accumulator.
protected SHA!(maxGather * 8, 512) accumulator;
/**
* Params:
* maxSources = Maximum amount of entropy sources can be set.
* allocator = Allocator to allocate entropy sources available on the
* system.
* Returns: Minimum bytes required from the entropy source.
*/
this(const size_t maxSources = 20,
shared Allocator allocator = defaultAllocator) @nogc
in
override @property ubyte threshold() const @nogc nothrow pure @safe
{
assert(maxSources > 0 && maxSources <= ubyte.max);
assert(allocator !is null);
}
body
{
allocator.resize(sources, maxSources);
version (linux)
{
this ~= allocator.make!PlatformEntropySource;
}
return 32;
}
/**
* Returns: Amount of the registered entropy sources.
* Returns: Whether this entropy source is strong.
*/
@property ubyte sourceCount() const pure nothrow @safe @nogc
override @property bool strong() const @nogc nothrow pure @safe
{
return sourceCount_;
return true;
}
/**
* Add an entropy source.
* Poll the entropy source.
*
* Params:
* source = Entropy source.
* output = Buffer to save the generate random sequence (the method will
* to fill the buffer).
*
* Returns: $(D_PSYMBOL this).
*
* See_Also:
* $(D_PSYMBOL EntropySource)
* Returns: Number of bytes that were copied to the $(D_PARAM output)
* or nothing on error.
*/
Entropy opOpAssign(string Op)(EntropySource source) pure nothrow @safe @nogc
if (Op == "~")
in
override Nullable!ubyte poll(out ubyte[maxGather] output)
@nogc nothrow @safe
out (length)
{
assert(sourceCount_ <= sources.length);
assert(length.isNull || length.get <= maxGather);
}
body
{
sources[sourceCount_++] = source;
return this;
}
/**
* Returns: Generated random sequence.
*
* Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was
* registered or it failed.
*/
@property ubyte[blockSize] random() @nogc
in
{
assert(sourceCount_ > 0, "No entropy sources defined.");
}
body
{
bool haveStrong;
ushort done;
ubyte[blockSize] output;
do
{
ubyte[maxGather] buffer;
(() @trusted => arc4random_buf(output.ptr, output.length))();
return Nullable!ubyte(cast(ubyte) (output.length));
}
}
}
else version (Windows)
{
import core.sys.windows.basetsd : ULONG_PTR;
import core.sys.windows.winbase : GetLastError;
import core.sys.windows.wincrypt;
import core.sys.windows.windef : BOOL, DWORD, PBYTE;
import core.sys.windows.winerror : NTE_BAD_KEYSET;
import core.sys.windows.winnt : LPCSTR, LPCWSTR;
// Run through our entropy sources
for (ubyte i; i < sourceCount; ++i)
private extern(Windows) @nogc nothrow
{
auto outputLength = sources[i].poll(buffer);
if (!outputLength.isNull)
{
if (outputLength > 0)
{
update(i, buffer, outputLength);
sources[i].size = cast(ushort) (sources[i].size + outputLength);
}
if (sources[i].size < sources[i].threshold)
{
continue;
}
else if (sources[i].strong)
{
haveStrong = true;
}
}
done = 257;
}
}
while (++done < 256);
if (!haveStrong)
{
throw allocator.make!EntropyException("No strong entropy source defined.");
BOOL CryptGenRandom(HCRYPTPROV, DWORD, PBYTE);
BOOL CryptAcquireContextA(HCRYPTPROV*, LPCSTR, LPCSTR, DWORD, DWORD);
BOOL CryptAcquireContextW(HCRYPTPROV*, LPCWSTR, LPCWSTR, DWORD, DWORD);
BOOL CryptReleaseContext(HCRYPTPROV, ULONG_PTR);
}
output = accumulator.finish();
// Reset accumulator and counters and recycle existing entropy
accumulator.start();
// Perform second SHA-512 on entropy
output = sha512Of(output);
for (ubyte i = 0; i < sourceCount; ++i)
private bool initCryptGenRandom(scope ref HCRYPTPROV hProvider)
@nogc nothrow @trusted
{
sources[i].size = 0;
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa379886(v=vs.85).aspx
// For performance reasons, we recommend that you set the pszContainer
// parameter to NULL and the dwFlags parameter to CRYPT_VERIFYCONTEXT
// in all situations where you do not require a persisted key.
// CRYPT_SILENT is intended for use with applications for which the UI
// cannot be displayed by the CSP.
if (!CryptAcquireContextW(&hProvider,
null,
null,
PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
{
if (GetLastError() != NTE_BAD_KEYSET)
{
return false;
}
// Attempt to create default container
if (!CryptAcquireContextA(&hProvider,
null,
null,
PROV_RSA_FULL,
CRYPT_NEWKEYSET | CRYPT_SILENT))
{
return false;
}
}
return true;
}
class PlatformEntropySource : EntropySource
{
private HCRYPTPROV hProvider;
/**
* Uses CryptGenRandom.
*/
this() @nogc
{
if (!initCryptGenRandom(hProvider))
{
throw defaultAllocator.make!EntropyException("CryptAcquireContextW failed.");
}
assert(hProvider > 0, "hProvider not properly initialized.");
}
~this() @nogc nothrow @safe
{
if (hProvider > 0)
{
(() @trusted => CryptReleaseContext(hProvider, 0))();
}
return output;
}
/**
* Update entropy accumulator.
* Returns: Minimum bytes required from the entropy source.
*/
override @property ubyte threshold() const @nogc nothrow pure @safe
{
return 32;
}
/**
* Returns: Whether this entropy source is strong.
*/
override @property bool strong() const @nogc nothrow pure @safe
{
return true;
}
/**
* Poll the entropy source.
*
* Params:
* sourceId = Entropy source index in $(D_PSYMBOL sources).
* data = Data got from the entropy source.
* length = Length of the received data.
* output = Buffer to save the generate random sequence (the method will
* to fill the buffer).
*
* Returns: Number of bytes that were copied to the $(D_PARAM output)
* or nothing on error.
*/
protected void update(in ubyte sourceId,
ref ubyte[maxGather] data,
ubyte length) pure nothrow @safe @nogc
override Nullable!ubyte poll(out ubyte[maxGather] output)
@nogc nothrow @safe
out (length)
{
ubyte[2] header;
if (length > blockSize)
assert(length.isNull || length.get <= maxGather);
}
do
{
data[0 .. 64] = sha512Of(data);
length = blockSize;
}
Nullable!ubyte ret;
header[0] = sourceId;
header[1] = length;
accumulator.put(header);
accumulator.put(data[0 .. length]);
assert(hProvider > 0, "hProvider not properly initialized");
if ((() @trusted => CryptGenRandom(hProvider, output.length, cast(PBYTE) output.ptr))())
{
ret = cast(ubyte) (output.length);
}
return ret;
}
}
}

View File

@ -1,81 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This module contains the interface for implementing custom allocators.
*
* Allocators are classes encapsulating memory allocation strategy. This allows
* to decouple memory management from the algorithms and the data.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/allocator.d,
* tanya/memory/allocator.d)
*/
module tanya.memory.allocator;
/**
* Abstract class implementing a basic allocator.
*/
interface Allocator
{
/**
* Returns: Alignment offered.
*/
@property uint alignment() const shared pure nothrow @safe @nogc;
/**
* Allocates $(D_PARAM size) bytes of memory.
*
* Params:
* size = Amount of memory to allocate.
*
* Returns: Pointer to the new allocated memory.
*/
void[] allocate(const size_t size) shared pure nothrow @nogc;
/**
* Deallocates a memory block.
*
* Params:
* p = A pointer to the memory block to be freed.
*
* Returns: Whether the deallocation was successful.
*/
bool deallocate(void[] p) shared pure nothrow @nogc;
/**
* Increases or decreases the size of a memory block.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: Pointer to the allocated memory.
*/
bool reallocate(ref void[] p, const size_t size) shared pure nothrow @nogc;
/**
* Reallocates a memory block in place if possible or returns
* $(D_KEYWORD false). This function cannot be used to allocate or
* deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
* $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
*/
bool reallocateInPlace(ref void[] p, const size_t size)
shared pure nothrow @nogc;
}
package template GetPureInstance(T : Allocator)
{
alias GetPureInstance = shared(T) function()
pure nothrow @nogc;
}

View File

@ -1,441 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Implementions of functions found in $(D_PSYMBOL tanya.memory.op) for x64.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/arch/x86_64.d,
* tanya/memory/arch/x86_64.d)
*/
module tanya.memory.arch.x86_64;
import tanya.memory.op;
version (D_InlineAsm_X86_64):
pragma(inline, true)
package (tanya.memory) void copy(const void[] source, void[] target)
pure nothrow @system @nogc
{
asm pure nothrow @nogc
{
naked;
// RDI and RSI should be preserved.
mov RAX, RDI;
mov R8, RSI;
}
// Set the registers for movsb/movsq.
version (Windows) asm pure nothrow @nogc
{
// RDX - source.
// RCX - target.
mov RDI, [ RCX + 8 ];
mov RSI, [ RDX + 8 ];
mov RDX, [ RDX ];
}
else asm pure nothrow @nogc
{
// RDX - source length.
// RCX - source data.
// RDI - target length
// RSI - target data.
mov RDI, RSI;
mov RSI, RCX;
}
asm pure nothrow @nogc
{
cmp RDX, 0x08;
jc aligned_1;
test EDI, 0x07;
jz aligned_8;
naligned:
movsb;
dec RDX;
test EDI, 0x07;
jnz naligned;
aligned_8:
mov RCX, RDX;
shr RCX, 0x03;
rep;
movsq;
and EDX, 0x07;
jz end;
aligned_1:
// Write the remaining bytes.
mov RCX, RDX;
rep;
movsb;
end: // Restore registers.
mov RSI, R8;
mov RDI, RAX;
ret;
}
}
package (tanya.memory) template fill(ubyte Byte)
{
private enum const(char[]) MovArrayPointer(string Destination)()
{
string asmCode = "asm pure nothrow @nogc { mov ";
version (Windows)
{
asmCode ~= Destination ~ ", [ RCX + 8 ];";
}
else
{
asmCode ~= Destination ~ ", RSI;";
}
return asmCode ~ "}";
}
pragma(inline, true)
void fill(void[] memory)
{
asm pure nothrow @nogc
{
naked;
}
version (Windows) asm pure nothrow @nogc
{
/*
* RCX - array.
*/
mov R8, [ RCX ];
}
else asm pure nothrow @nogc
{
/*
* RSI - pointer.
* RDI - length.
*/
mov R8, RDI;
}
mixin(MovArrayPointer!"R9");
asm pure nothrow @nogc
{
// Check for zero length.
test R8, R8;
jz end;
}
// Set 128- and 64-bit registers to values we want to fill with.
static if (Byte == 0)
{
asm pure nothrow @nogc
{
xor RAX, RAX;
pxor XMM0, XMM0;
}
}
else
{
enum ulong FilledBytes = FilledBytes!Byte;
asm pure nothrow @nogc
{
mov RAX, FilledBytes;
movq XMM0, RAX;
movlhps XMM0, XMM0;
}
}
asm pure nothrow @nogc
{
// Check if the pointer is aligned to a 16-byte boundary.
and R9, -0x10;
}
// Compute the number of misaligned bytes.
mixin(MovArrayPointer!"R10");
asm pure nothrow @nogc
{
sub R10, R9;
test R10, R10;
jz aligned;
// Get the number of bytes to be written until we are aligned.
mov RDX, 0x10;
sub RDX, R10;
}
mixin(MovArrayPointer!"R9");
asm pure nothrow @nogc
{
naligned:
mov [ R9 ], AL; // Write a byte.
// Advance the pointer. Decrease the total number of bytes
// and the misaligned ones.
inc R9;
dec RDX;
dec R8;
// Checks if we are aligned.
test RDX, RDX;
jnz naligned;
aligned:
// Checks if we're done writing bytes.
test R8, R8;
jz end;
// Write 1 byte at a time.
cmp R8, 8;
jl aligned_1;
// Write 8 bytes at a time.
cmp R8, 16;
jl aligned_8;
// Write 16 bytes at a time.
cmp R8, 32;
jl aligned_16;
// Write 32 bytes at a time.
cmp R8, 64;
jl aligned_32;
aligned_64:
movdqa [ R9 ], XMM0;
movdqa [ R9 + 16 ], XMM0;
movdqa [ R9 + 32 ], XMM0;
movdqa [ R9 + 48 ], XMM0;
add R9, 64;
sub R8, 64;
cmp R8, 64;
jge aligned_64;
// Checks if we're done writing bytes.
test R8, R8;
jz end;
// Write 1 byte at a time.
cmp R8, 8;
jl aligned_1;
// Write 8 bytes at a time.
cmp R8, 16;
jl aligned_8;
// Write 16 bytes at a time.
cmp R8, 32;
jl aligned_16;
aligned_32:
movdqa [ R9 ], XMM0;
movdqa [ R9 + 16 ], XMM0;
add R9, 32;
sub R8, 32;
// Checks if we're done writing bytes.
test R8, R8;
jz end;
// Write 1 byte at a time.
cmp R8, 8;
jl aligned_1;
// Write 8 bytes at a time.
cmp R8, 16;
jl aligned_8;
aligned_16:
movdqa [ R9 ], XMM0;
add R9, 16;
sub R8, 16;
// Checks if we're done writing bytes.
test R8, R8;
jz end;
// Write 1 byte at a time.
cmp R8, 8;
jl aligned_1;
aligned_8:
mov [ R9 ], RAX;
add R9, 8;
sub R8, 8;
// Checks if we're done writing bytes.
test R8, R8;
jz end;
aligned_1:
mov [ R9 ], AL;
inc R9;
dec R8;
test R8, R8;
jnz aligned_1;
end:
ret;
}
}
}
pragma(inline, true)
package (tanya.memory) void copyBackward(const void[] source, void[] target)
pure nothrow @system @nogc
{
asm pure nothrow @nogc
{
naked;
// Save the registers should be restored.
mov R8, RSI;
mov R9, RDI;
}
// Prepare the registers for movsb.
version (Windows) asm pure nothrow @nogc
{
// RDX - source.
// RCX - target.
mov RAX, [ RCX + 8 ];
mov R10, [ RDX + 8 ];
mov RCX, [ RDX ];
lea RDI, [ RAX + RCX - 1 ];
lea RSI, [ R10 + RCX - 1 ];
}
else asm pure nothrow @nogc
{
// RDX - source length.
// RCX - source data.
// RDI - target length
// RSI - target data.
lea RDI, [ RSI + RDX - 1 ];
lea RSI, [ RCX + RDX - 1 ];
mov RCX, RDX;
}
asm pure nothrow @nogc
{
std; // Set the direction flag.
rep;
movsb;
cld; // Clear the direction flag.
// Restore registers.
mov RDI, R9;
mov RSI, R8;
ret;
}
}
pragma(inline, true)
package (tanya.memory) int cmp(const void[] r1, const void[] r2)
pure nothrow @system @nogc
{
asm pure nothrow @nogc
{
naked;
// RDI and RSI should be preserved.
mov R9, RDI;
mov R8, RSI;
}
// Set the registers for cmpsb/cmpsq.
version (Windows) asm pure nothrow @nogc
{
// RDX - r1.
// RCX - r2.
mov RDI, [ RCX + 8 ];
mov RSI, [ RDX + 8 ];
mov RDX, [ RDX ];
mov RCX, [ RCX ];
}
else asm pure nothrow @nogc
{
// RDX - r1 length.
// RCX - r1 data.
// RDI - r2 length
// RSI - r2 data.
mov RSI, RCX;
mov RCX, RDI;
mov RDI, R8;
}
asm pure nothrow @nogc
{
// Compare the lengths.
cmp RDX, RCX;
jl less;
jg greater;
// Check if we're aligned.
cmp RDX, 0x08;
jc aligned_1;
test EDI, 0x07;
jz aligned_8;
naligned:
cmpsb;
jl less;
jg greater;
dec RDX;
test EDI, 0x07;
jnz naligned;
aligned_8:
mov RCX, RDX;
shr RCX, 0x03;
repe;
cmpsq;
jl less;
jg greater;
and EDX, 0x07;
jz equal;
aligned_1: // Compare the remaining bytes.
mov RCX, RDX;
repe;
cmpsb;
jl less;
jg greater;
equal:
xor RAX, RAX; // Return 0.
jmp end;
greater:
mov RAX, 1;
jmp end;
less:
mov RAX, -1;
jmp end;
end: // Restore registers.
mov RSI, R8;
mov RDI, R9;
ret;
}
}

View File

@ -1,404 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Set of operations on memory blocks.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/op.d,
* tanya/memory/op.d)
*/
module tanya.memory.op;
version (D_InlineAsm_X86_64)
{
static import tanya.memory.arch.x86_64;
}
private enum alignMask = size_t.sizeof - 1;
/**
* Copies $(D_PARAM source) into $(D_PARAM target).
*
* $(D_PARAM source) and $(D_PARAM target) shall not overlap so that
* $(D_PARAM source) points ahead of $(D_PARAM target).
*
* $(D_PARAM target) shall have enough space for $(D_INLINECODE source.length)
* elements.
*
* Params:
* source = Memory to copy from.
* target = Destination memory.
*
* See_Also: $(D_PSYMBOL copyBackward).
*
* Precondition: $(D_INLINECODE source.length <= target.length).
*/
void copy(const void[] source, void[] target) pure nothrow @trusted @nogc
in
{
assert(source.length <= target.length);
}
body
{
version (D_InlineAsm_X86_64)
{
tanya.memory.arch.x86_64.copy(source, target);
}
else // Naive implementation.
{
auto source1 = cast(const(ubyte)*) source;
auto target1 = cast(ubyte*) target;
auto count = source.length;
// Check if the pointers are aligned or at least can be aligned
// properly.
ushort naligned = (cast(size_t) source.ptr) & alignMask;
if (naligned == ((cast(size_t) target.ptr) & alignMask))
{
// Align the pointers if possible.
if (naligned != 0)
{
count -= naligned;
while (naligned--)
{
*target1++ = *source1++;
}
}
// Copy size_t.sizeof bytes at once.
auto longSource = cast(const(size_t)*) source1;
auto longTarget = cast(size_t*) target1;
for (; count >= size_t.sizeof; count -= size_t.sizeof)
{
*longTarget++ = *longSource++;
}
// Adjust the original pointers.
source1 = cast(const(ubyte)*) longSource;
target1 = cast(ubyte*) longTarget;
}
// Copy the remaining bytes by one.
while (count--)
{
*target1++ = *source1++;
}
}
}
///
pure nothrow @safe @nogc unittest
{
ubyte[9] source = [1, 2, 3, 4, 5, 6, 7, 8, 9];
ubyte[9] target;
source.copy(target);
assert(cmp(source, target) == 0);
}
private pure nothrow @safe @nogc unittest
{
{
ubyte[0] source, target;
source.copy(target);
}
{
ubyte[1] source = [1];
ubyte[1] target;
source.copy(target);
assert(target[0] == 1);
}
{
ubyte[8] source = [1, 2, 3, 4, 5, 6, 7, 8];
ubyte[8] target;
source.copy(target);
assert(cmp(source, target) == 0);
}
}
/*
* size_t value each of which bytes is set to `Byte`.
*/
package template FilledBytes(ubyte Byte, ubyte I = 0)
{
static if (I == size_t.sizeof)
{
enum size_t FilledBytes = Byte;
}
else
{
enum size_t FilledBytes = (FilledBytes!(Byte, I + 1) << 8) | Byte;
}
}
/**
* Fills $(D_PARAM memory) with single $(D_PARAM Byte)s.
*
* Param:
* Byte = The value to fill $(D_PARAM memory) with.
* memory = Memory block.
*/
void fill(ubyte Byte = 0)(void[] memory) @trusted
{
version (D_InlineAsm_X86_64)
{
tanya.memory.arch.x86_64.fill!Byte(memory);
}
else // Naive implementation.
{
auto n = memory.length;
ubyte* vp = cast(ubyte*) memory.ptr;
// Align.
while (((cast(size_t) vp) & alignMask) != 0)
{
*vp++ = Byte;
--n;
}
// Set size_t.sizeof bytes at ones.
auto sp = cast(size_t*) vp;
while (n / size_t.sizeof > 0)
{
*sp++ = FilledBytes!Byte;
n -= size_t.sizeof;
}
// Write the remaining bytes.
vp = cast(ubyte*) sp;
while (n--)
{
*vp = Byte;
++vp;
}
}
}
///
pure nothrow @safe @nogc unittest
{
ubyte[9] memory = [1, 2, 3, 4, 5, 6, 7, 8, 9];
memory.fill!0();
foreach (ubyte v; memory)
{
assert(v == 0);
}
}
// Stress test. Checks that `fill` can handle unaligned pointers and different
// lengths.
pure nothrow @safe @nogc private unittest
{
ubyte[192] memory;
foreach (j; 0 .. 192)
{
foreach (ubyte i, ref ubyte v; memory[j .. $])
{
v = i;
}
fill(memory[j .. $]);
foreach (ubyte v; memory[j .. $])
{
assert(v == 0);
}
fill!1(memory[j .. $]);
foreach (ubyte v; memory[j .. $])
{
assert(v == 1);
}
}
}
/**
* Copies starting from the end of $(D_PARAM source) into the end of
* $(D_PARAM target).
*
* $(D_PSYMBOL copyBackward) copies the elements in reverse order, but the
* order of elements in the $(D_PARAM target) is exactly the same as in the
* $(D_PARAM source).
*
* $(D_PARAM source) and $(D_PARAM target) shall not overlap so that
* $(D_PARAM target) points ahead of $(D_PARAM source).
*
* $(D_PARAM target) shall have enough space for $(D_INLINECODE source.length)
* elements.
*
* Params:
* source = Memory to copy from.
* target = Destination memory.
*
* See_Also: $(D_PSYMBOL copy).
*
* Precondition: $(D_INLINECODE source.length <= target.length).
*/
void copyBackward(const void[] source, void[] target) pure nothrow @trusted @nogc
in
{
assert(source.length <= target.length);
}
body
{
version (D_InlineAsm_X86_64)
{
tanya.memory.arch.x86_64.copyBackward(source, target);
}
else // Naive implementation.
{
auto count = source.length;
// Try to align the pointers if possible.
if (((cast(size_t) source.ptr) & alignMask) == ((cast(size_t) target.ptr) & alignMask))
{
while (((cast(size_t) (source.ptr + count)) & alignMask) != 0)
{
if (!count--)
{
return;
}
(cast(ubyte[]) target)[count]
= (cast(const(ubyte)[]) source)[count];
}
}
// Write as long we're aligned.
for (; count >= size_t.sizeof; count -= size_t.sizeof)
{
*(cast(size_t*) (target.ptr + count - size_t.sizeof))
= *(cast(const(size_t)*) (source.ptr + count - size_t.sizeof));
}
// Write the remaining bytes.
while (count--)
{
(cast(ubyte[]) target)[count]
= (cast(const(ubyte)[]) source)[count];
}
}
}
///
pure nothrow @safe @nogc unittest
{
ubyte[6] mem = [ 'a', 'a', 'b', 'b', 'c', 'c' ];
ubyte[6] expected = [ 'a', 'a', 'a', 'a', 'b', 'b' ];
copyBackward(mem[0 .. 4], mem[2 .. $]);
assert(cmp(expected, mem) == 0);
}
private nothrow @safe @nogc unittest
{
ubyte[9] r1 = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' ];
ubyte[9] r2;
copyBackward(r1, r2);
assert(cmp(r1, r2) == 0);
}
/**
* Compares two memory areas $(D_PARAM r1) and $(D_PARAM r2).
*
* $(D_PSYMBOL cmp) returns a positive integer if
* $(D_INLINECODE r1.length > r2.length) or the first `n` compared bytes of
* $(D_PARAM r1) found to be greater than the first `n` bytes of $(D_PARAM r2),
*
* $(D_PSYMBOL cmp) returns a negative integer if
* $(D_INLINECODE r2.length > r1.length) or the first `n` compared bytes of
* $(D_PARAM r1) found to be less than the first `n` bytes of $(D_PARAM r2),
*
* `0` is returned otherwise.
*
* Returns: Positive integer if $(D_INLINECODE r1 > r2),
* negative integer if $(D_INLINECODE r2 > r1),
* `0` if $(D_INLINECODE r1 == r2).
*/
int cmp(const void[] r1, const void[] r2) pure nothrow @trusted @nogc
{
version (D_InlineAsm_X86_64)
{
return tanya.memory.arch.x86_64.cmp(r1, r2);
}
else // Naive implementation.
{
if (r1.length > r2.length)
{
return 1;
}
else if (r1.length < r2.length)
{
return -1;
}
auto p1 = cast(const(ubyte)*) r1;
auto p2 = cast(const(ubyte)*) r2;
auto count = r1.length;
// Check if the pointers are aligned or at least can be aligned
// properly.
if (((cast(size_t) p1) & alignMask) == ((cast(size_t) p2) & alignMask))
{
// Align the pointers if possible.
for (; ((cast(size_t) p1) & alignMask) != 0; ++p1, ++p2, --count)
{
if (*p1 != *p2)
{
return *p1 - *p2;
}
}
// Compare size_t.sizeof bytes at once.
for (; count >= size_t.sizeof; count -= size_t.sizeof)
{
if (*(cast(const(size_t)*) p1) > *(cast(const(size_t)*) p2))
{
return 1;
}
else if (*(cast(const(size_t)*) p1) < *(cast(const(size_t)*) p2))
{
return -1;
}
p1 += size_t.sizeof;
p2 += size_t.sizeof;
}
}
// Compare the remaining bytes by one.
for (; count--; ++p1, ++p2)
{
if (*p1 != *p2)
{
return *p1 - *p2;
}
}
return 0;
}
}
///
pure nothrow @safe @nogc unittest
{
ubyte[4] r1 = [ 'a', 'b', 'c', 'd' ];
ubyte[3] r2 = [ 'c', 'a', 'b' ];
assert(cmp(r1[0 .. 3], r2[]) < 0);
assert(cmp(r2[], r1[0 .. 3]) > 0);
assert(cmp(r1, r2) > 0);
assert(cmp(r2, r1) < 0);
}
private pure nothrow @safe @nogc unittest
{
ubyte[16] r1 = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
];
ubyte[16] r2 = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
];
assert(cmp(r1, r2) == 0);
assert(cmp(r1[1 .. $], r2[1 .. $]) == 0);
assert(cmp(r1[0 .. $ - 1], r2[0 .. $ - 1]) == 0);
assert(cmp(r1[0 .. 8], r2[0 .. 8]) == 0);
}

View File

@ -1,453 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This module contains functions that manipulate template type lists as well
* as algorithms to perform arbitrary compile-time computations.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/meta/metafunction.d,
* tanya/meta/metafunction.d)
*/
module tanya.meta.metafunction;
version (unittest)
{
import tanya.meta.trait;
}
/**
* Creates an alias for $(D_PARAM T).
*
* In contrast to the $(D_KEYWORD alias)-keyword $(D_PSYMBOL Alias) can alias
* any kind of D symbol that can be used as argument to template alias
* parameters.
*
* $(UL
* $(LI Types)
* $(LI Local and global names)
* $(LI Module names)
* $(LI Template names)
* $(LI Template instance names)
* $(LI Literals)
* )
*
* Params:
* T = A symbol.
*
* Returns: An alias for $(D_PARAM T).
*
* See_Also: $(LINK2 https://dlang.org/spec/template.html#aliasparameters,
* Template Alias Parameters).
*/
alias Alias(alias T) = T;
/// Ditto.
alias Alias(T) = T;
///
pure nothrow @safe @nogc unittest
{
static assert(is(Alias!int));
static assert(is(typeof(Alias!5)));
static assert(is(typeof(Alias!(() {}))));
int i;
static assert(is(typeof(Alias!i)));
}
/**
* Params:
* Args = List of symbols.
*
* Returns: An alias for sequence $(D_PARAM Args).
*
* See_Also: $(D_PSYMBOL Alias).
*/
alias AliasSeq(Args...) = Args;
///
pure nothrow @safe @nogc unittest
{
static assert(is(typeof({ alias T = AliasSeq!(short, 5); })));
static assert(is(typeof({ alias T = AliasSeq!(int, short, 5); })));
static assert(is(typeof({ alias T = AliasSeq!(() {}, short, 5); })));
static assert(is(typeof({ alias T = AliasSeq!(); })));
static assert(AliasSeq!().length == 0);
static assert(AliasSeq!(int, short, 5).length == 3);
}
/**
* Tests whether all the items of $(D_PARAM L) satisfy the condition
* $(D_PARAM F).
*
* $(D_PARAM F) is a template that accepts one parameter and returns a boolean,
* so $(D_INLINECODE F!([0]) && F!([1])) and so on, can be called.
*
* Params:
* F = Template predicate.
* L = List of items to test.
*
* Returns: $(D_KEYWORD true) if all the items of $(D_PARAM L) satisfy
* $(D_PARAM F), $(D_KEYWORD false) otherwise.
*/
template allSatisfy(alias F, L...)
{
static if (L.length == 0)
{
enum bool allSatisfy = true;
}
else static if (F!(L[0]))
{
enum bool allSatisfy = allSatisfy!(F, L[1 .. $]);
}
else
{
enum bool allSatisfy = false;
}
}
///
pure nothrow @safe @nogc unittest
{
static assert(allSatisfy!(isSigned, int, short, byte, long));
static assert(!allSatisfy!(isUnsigned, uint, ushort, float, ulong));
}
/**
* Tests whether any of the items of $(D_PARAM L) satisfy the condition
* $(D_PARAM F).
*
* $(D_PARAM F) is a template that accepts one parameter and returns a boolean,
* so $(D_INLINECODE F!([0]) && F!([1])) and so on, can be called.
*
* Params:
* F = Template predicate.
* L = List of items to test.
*
* Returns: $(D_KEYWORD true) if any of the items of $(D_PARAM L) satisfy
* $(D_PARAM F), $(D_KEYWORD false) otherwise.
*/
template anySatisfy(alias F, L...)
{
static if (L.length == 0)
{
enum bool anySatisfy = false;
}
else static if (F!(L[0]))
{
enum bool anySatisfy = true;
}
else
{
enum bool anySatisfy = anySatisfy!(F, L[1 .. $]);
}
}
///
pure nothrow @safe @nogc unittest
{
static assert(anySatisfy!(isSigned, int, short, byte, long));
static assert(anySatisfy!(isUnsigned, uint, ushort, float, ulong));
static assert(!anySatisfy!(isSigned, uint, ushort, ulong));
}
private template indexOf(ptrdiff_t i, Args...)
if (Args.length > 0)
{
static if (Args.length == 1)
{
enum ptrdiff_t indexOf = -1;
}
else static if (is(Args[0] == Args[1])
|| (is(typeof(Args[0] == Args[1])) && (Args[0] == Args[1])))
{
enum ptrdiff_t indexOf = i;
}
else
{
enum ptrdiff_t indexOf = indexOf!(i + 1,
AliasSeq!(Args[0], Args[2 .. $]));
}
}
/**
* Returns the index of the first occurrence of $(D_PARAM T) in $(D_PARAM L).
* `-1` is returned if $(D_PARAM T) is not found.
*
* Params:
* T = The type to search for.
* L = Type list.
*
* Returns: The index of the first occurence of $(D_PARAM T) in $(D_PARAM L).
*/
template staticIndexOf(T, L...)
{
enum ptrdiff_t staticIndexOf = indexOf!(0, AliasSeq!(T, L));
}
/// Ditto.
template staticIndexOf(alias T, L...)
{
enum ptrdiff_t staticIndexOf = indexOf!(0, AliasSeq!(T, L));
}
///
pure nothrow @safe @nogc unittest
{
static assert(staticIndexOf!(int) == -1);
static assert(staticIndexOf!(int, int) == 0);
static assert(staticIndexOf!(int, float, double, int, real) == 2);
static assert(staticIndexOf!(3, () {}, uint, 5, 3) == 3);
}
/**
* Instantiates the template $(D_PARAM T) with $(D_PARAM ARGS).
*
* Params:
* T = Template.
* Args = Template parameters.
*
* Returns: Instantiated template.
*/
alias Instantiate(alias T, Args...) = T!Args;
/**
* Combines multiple templates with logical AND. So $(D_PSYMBOL templateAnd)
* evaluates to $(D_INLINECODE Preds[0] && Preds[1] && Preds[2]) and so on.
*
* Empty $(D_PARAM Preds) evaluates to $(D_KEYWORD true).
*
* Params:
* Preds = Template predicates.
*
* Returns: The constructed template.
*/
template templateAnd(Preds...)
{
template templateAnd(T...)
{
static if (Preds.length == 0)
{
enum bool templateAnd = true;
}
else static if (Instantiate!(Preds[0], T))
{
alias templateAnd = Instantiate!(.templateAnd!(Preds[1 .. $]), T);
}
else
{
enum bool templateAnd = false;
}
}
}
///
pure nothrow @safe @nogc unittest
{
alias isMutableInt = templateAnd!(isIntegral, isMutable);
static assert(isMutableInt!int);
static assert(!isMutableInt!(const int));
static assert(!isMutableInt!float);
alias alwaysTrue = templateAnd!();
static assert(alwaysTrue!int);
alias isIntegral = templateAnd!(.isIntegral);
static assert(isIntegral!int);
static assert(isIntegral!(const int));
static assert(!isIntegral!float);
}
/**
* Combines multiple templates with logical OR. So $(D_PSYMBOL templateOr)
* evaluates to $(D_INLINECODE Preds[0] || Preds[1] || Preds[2]) and so on.
*
* Empty $(D_PARAM Preds) evaluates to $(D_KEYWORD false).
*
* Params:
* Preds = Template predicates.
*
* Returns: The constructed template.
*/
template templateOr(Preds...)
{
template templateOr(T...)
{
static if (Preds.length == 0)
{
enum bool templateOr = false;
}
else static if (Instantiate!(Preds[0], T))
{
enum bool templateOr = true;
}
else
{
alias templateOr = Instantiate!(.templateOr!(Preds[1 .. $]), T);
}
}
}
///
pure nothrow @safe @nogc unittest
{
alias isMutableOrInt = templateOr!(isIntegral, isMutable);
static assert(isMutableOrInt!int);
static assert(isMutableOrInt!(const int));
static assert(isMutableOrInt!float);
static assert(!isMutableOrInt!(const float));
alias alwaysFalse = templateOr!();
static assert(!alwaysFalse!int);
alias isIntegral = templateOr!(.isIntegral);
static assert(isIntegral!int);
static assert(isIntegral!(const int));
static assert(!isIntegral!float);
}
/**
* Params:
* pred = Template predicate.
*
* Returns: Negated $(D_PARAM pred).
*/
template templateNot(alias pred)
{
enum bool templateNot(T...) = !pred!T;
}
///
pure nothrow @safe @nogc unittest
{
alias isNotIntegral = templateNot!isIntegral;
static assert(!isNotIntegral!int);
static assert(isNotIntegral!(char[]));
}
/**
* Tests whether $(D_PARAM L) is sorted in ascending order according to
* $(D_PARAM cmp).
*
* $(D_PARAM cmp) can evaluate to:
* $(UL
* $(LI $(D_KEYWORD bool): $(D_KEYWORD true) means
* $(D_INLINECODE a[i] < a[i + 1]).)
* $(LI $(D_KEYWORD int): a negative number means that
* $(D_INLINECODE a[i] < a[i + 1]), a positive number that
* $(D_INLINECODE a[i] > a[i + 1]), `0` if they equal.)
* )
*
* Params:
* cmp = Comparison template.
* L = Arguments.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM L) is sorted, $(D_KEYWORD false)
* if not.
*/
template staticIsSorted(alias cmp, L...)
{
static if (L.length <= 1)
{
enum bool staticIsSorted = true;
}
else
{
// `L` is sorted if the both halves are sorted.
enum bool halves = staticIsSorted!(cmp, L[0 .. $ / 2])
&& staticIsSorted!(cmp, L[$ / 2 .. $]);
// Compare the boundary values of the havles.
enum result = cmp!(L[$ / 2], L[$ / 2 - 1]);
static if (is(typeof(result) == bool))
{
enum bool staticIsSorted = !result && halves;
}
else
{
enum bool staticIsSorted = result >= 0 && halves;
}
}
}
///
pure nothrow @safe @nogc unittest
{
enum cmp(T, U) = T.sizeof < U.sizeof;
static assert(staticIsSorted!(cmp));
static assert(staticIsSorted!(cmp, byte));
static assert(staticIsSorted!(cmp, byte, ubyte, short, uint));
static assert(!staticIsSorted!(cmp, long, byte, ubyte, short, uint));
}
private pure nothrow @safe @nogc unittest
{
enum cmp(int x, int y) = x - y;
static assert(staticIsSorted!(cmp));
static assert(staticIsSorted!(cmp, 1));
static assert(staticIsSorted!(cmp, 1, 2, 2));
static assert(staticIsSorted!(cmp, 1, 2, 2, 4));
static assert(staticIsSorted!(cmp, 1, 2, 2, 4, 8));
static assert(!staticIsSorted!(cmp, 32, 2, 2, 4, 8));
static assert(staticIsSorted!(cmp, 32, 32));
}
private pure nothrow @safe @nogc unittest
{
enum cmp(int x, int y) = x < y;
static assert(staticIsSorted!(cmp));
static assert(staticIsSorted!(cmp, 1));
static assert(staticIsSorted!(cmp, 1, 2, 2));
static assert(staticIsSorted!(cmp, 1, 2, 2, 4));
static assert(staticIsSorted!(cmp, 1, 2, 2, 4, 8));
static assert(!staticIsSorted!(cmp, 32, 2, 2, 4, 8));
static assert(staticIsSorted!(cmp, 32, 32));
}
/**
* Params:
* T = A template.
* Args = The first arguments for $(D_PARAM T).
*
* Returns: $(D_PARAM T) with $(D_PARAM Args) applied to it as its first
* arguments.
*/
template ApplyLeft(alias T, Args...)
{
alias ApplyLeft(U...) = T!(Args, U);
}
///
pure nothrow @safe @nogc unittest
{
alias allAreIntegral = ApplyLeft!(allSatisfy, isIntegral);
static assert(allAreIntegral!(int, uint));
static assert(!allAreIntegral!(int, float, uint));
}
/**
* Params:
* T = A template.
* Args = The last arguments for $(D_PARAM T).
*
* Returns: $(D_PARAM T) with $(D_PARAM Args) applied to it as itslast
* arguments.
*/
template ApplyRight(alias T, Args...)
{
alias ApplyRight(U...) = T!(U, Args);
}
///
pure nothrow @safe @nogc unittest
{
alias intIs = ApplyRight!(allSatisfy, int);
static assert(intIs!(isIntegral));
static assert(!intIs!(isUnsigned));
}

File diff suppressed because it is too large Load Diff

164
source/tanya/net/iface.d Normal file
View File

@ -0,0 +1,164 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Network interfaces.
*
* Copyright: Eugene Wissner 2018-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/net/iface.d,
* tanya/net/iface.d)
*/
module tanya.net.iface;
import tanya.algorithm.mutation;
import tanya.container.string;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range;
version (Windows)
{
import tanya.sys.windows.ifdef;
import tanya.sys.windows.iphlpapi;
}
else version (Posix)
{
import core.sys.posix.net.if_;
}
/**
* Converts the name of a network interface to its index.
*
* If an interface with the name $(D_PARAM name) cannot be found or another
* error occurres, returns 0.
*
* Params:
* name = Interface name.
*
* Returns: Returns interface index or 0.
*/
uint nameToIndex(R)(R name) @trusted
if (isInputRange!R && is(Unqual!(ElementType!R) == char) && hasLength!R)
{
version (Windows)
{
if (name.length > IF_MAX_STRING_SIZE)
{
return 0;
}
char[IF_MAX_STRING_SIZE + 1] buffer;
NET_LUID luid;
copy(name, buffer[]);
buffer[name.length] = '\0';
if (ConvertInterfaceNameToLuidA(buffer.ptr, &luid) != 0)
{
return 0;
}
NET_IFINDEX index;
if (ConvertInterfaceLuidToIndex(&luid, &index) == 0)
{
return index;
}
return 0;
}
else version (Posix)
{
if (name.length >= IF_NAMESIZE)
{
return 0;
}
char[IF_NAMESIZE] buffer;
copy(name, buffer[]);
buffer[name.length] = '\0';
return if_nametoindex(buffer.ptr);
}
}
///
@nogc nothrow @safe unittest
{
version (linux)
{
assert(nameToIndex("lo") == 1);
}
else version (Windows)
{
assert(nameToIndex("loopback_0") == 1);
}
else
{
assert(nameToIndex("lo0") == 1);
}
assert(nameToIndex("ecafretni") == 0);
}
/**
* Converts the index of a network interface to its name.
*
* If an interface with the $(D_PARAM index) cannot be found or another
* error occurres, returns an empty $(D_PSYMBOL String).
*
* Params:
* index = Interface index.
*
* Returns: Returns interface name or an empty $(D_PSYMBOL String).
*/
String indexToName(uint index) @nogc nothrow @trusted
{
import tanya.memory.op : findNullTerminated;
version (Windows)
{
NET_LUID luid;
if (ConvertInterfaceIndexToLuid(index, &luid) != 0)
{
return String();
}
char[IF_MAX_STRING_SIZE + 1] buffer;
if (ConvertInterfaceLuidToNameA(&luid,
buffer.ptr,
IF_MAX_STRING_SIZE + 1) != 0)
{
return String();
}
return String(findNullTerminated(buffer));
}
else version (Posix)
{
char[IF_NAMESIZE] buffer;
if (if_indextoname(index, buffer.ptr) is null)
{
return String();
}
return String(findNullTerminated(buffer));
}
}
/**
* $(D_PSYMBOL AddressFamily) specifies a communication domain; this selects
* the protocol family which will be used for communication.
*/
enum AddressFamily : int
{
unspec = 0, /// Unspecified.
local = 1, /// Local to host (pipes and file-domain).
unix = local, /// POSIX name for PF_LOCAL.
inet = 2, /// IP protocol family.
ax25 = 3, /// Amateur Radio AX.25.
ipx = 4, /// Novell Internet Protocol.
appletalk = 5, /// Appletalk DDP.
netrom = 6, /// Amateur radio NetROM.
bridge = 7, /// Multiprotocol bridge.
atmpvc = 8, /// ATM PVCs.
x25 = 9, /// Reserved for X.25 project.
inet6 = 10, /// IP version 6.
}

View File

@ -5,7 +5,7 @@
/**
* Internet utilities.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
@ -14,23 +14,9 @@
*/
module tanya.net.inet;
import std.math;
import std.range.primitives;
import std.traits;
version (unittest)
{
version (Windows)
{
import core.sys.windows.winsock2;
version = PlattformUnittest;
}
else version (Posix)
{
import core.sys.posix.arpa.inet;
version = PlattformUnittest;
}
}
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range;
/**
* Represents an unsigned integer as an $(D_KEYWORD ubyte) range.
@ -66,7 +52,7 @@ struct NetworkOrder(uint L)
private StorageType value;
private size_t size = L;
const pure nothrow @safe @nogc invariant
invariant
{
assert(this.size <= L);
}
@ -82,15 +68,15 @@ struct NetworkOrder(uint L)
* T = Value type.
* value = The value should be represented by this range.
*
* Precondition: $(D_INLINECODE value <= 2 ^^ (length * 8) - 1).
* Precondition: $(D_INLINECODE value <= (2 ^^ (L * 8)) - 1).
*/
this(T)(const T value)
this(T)(T value)
if (isUnsigned!T)
in
{
assert(value <= pow(2, L * 8) - 1);
assert(value <= (2 ^^ (L * 8)) - 1);
}
body
do
{
this.value = value & StorageType.max;
}
@ -105,7 +91,7 @@ struct NetworkOrder(uint L)
{
assert(this.length > 0);
}
body
do
{
return this.value & 0xff;
}
@ -120,7 +106,7 @@ struct NetworkOrder(uint L)
{
assert(this.length > 0);
}
body
do
{
return (this.value >> ((this.length - 1) * 8)) & 0xff;
}
@ -135,7 +121,7 @@ struct NetworkOrder(uint L)
{
assert(this.length > 0);
}
body
do
{
this.value >>= 8;
--this.size;
@ -151,7 +137,7 @@ struct NetworkOrder(uint L)
{
assert(this.length > 0);
}
body
do
{
this.value &= StorageType.max >> ((StorageType.sizeof - this.length) * 8);
--this.size;
@ -183,7 +169,7 @@ struct NetworkOrder(uint L)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
auto networkOrder = NetworkOrder!3(0xae34e2u);
assert(!networkOrder.empty);
@ -203,88 +189,6 @@ pure nothrow @safe @nogc unittest
assert(networkOrder.empty);
}
// Static.
private unittest
{
static assert(isBidirectionalRange!(NetworkOrder!4));
static assert(isBidirectionalRange!(NetworkOrder!8));
static assert(!is(NetworkOrder!9));
static assert(!is(NetworkOrder!1));
}
// Tests against the system's htonl, htons.
version (PlattformUnittest)
{
private unittest
{
for (uint counter; counter <= 8 * uint.sizeof; ++counter)
{
const value = pow(2, counter) - 1;
const inNetworkOrder = htonl(value);
const p = cast(ubyte*) &inNetworkOrder;
auto networkOrder = NetworkOrder!4(value);
assert(networkOrder.length == 4);
assert(!networkOrder.empty);
assert(networkOrder.front == *p);
assert(networkOrder.back == *(p + 3));
networkOrder.popBack();
assert(networkOrder.length == 3);
assert(networkOrder.front == *p);
assert(networkOrder.back == *(p + 2));
networkOrder.popFront();
assert(networkOrder.length == 2);
assert(networkOrder.front == *(p + 1));
assert(networkOrder.back == *(p + 2));
networkOrder.popFront();
assert(networkOrder.length == 1);
assert(networkOrder.front == *(p + 2));
assert(networkOrder.back == *(p + 2));
networkOrder.popBack();
assert(networkOrder.length == 0);
assert(networkOrder.empty);
}
for (ushort counter; counter <= 8 * ushort.sizeof; ++counter)
{
const value = cast(ushort) (pow(2, counter) - 1);
const inNetworkOrder = htons(value);
const p = cast(ubyte*) &inNetworkOrder;
auto networkOrder = NetworkOrder!2(value);
assert(networkOrder.length == 2);
assert(!networkOrder.empty);
assert(networkOrder.front == *p);
assert(networkOrder.back == *(p + 1));
networkOrder.popBack();
assert(networkOrder.length == 1);
assert(networkOrder.front == *p);
assert(networkOrder.back == *p);
networkOrder.popBack();
assert(networkOrder.length == 0);
assert(networkOrder.empty);
networkOrder = NetworkOrder!2(value);
networkOrder.popFront();
assert(networkOrder.length == 1);
assert(networkOrder.front == *(p + 1));
assert(networkOrder.back == *(p + 1));
networkOrder.popFront();
assert(networkOrder.length == 0);
assert(networkOrder.empty);
}
}
}
/**
* Converts the $(D_KEYWORD ubyte) input range $(D_PARAM range) to
* $(D_PARAM T).
@ -324,35 +228,9 @@ T toHostOrder(T = size_t, R)(R range)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
const value = 0xae34e2u;
auto networkOrder = NetworkOrder!4(value);
assert(networkOrder.toHostOrder() == value);
}
// Tests against the system's htonl, htons.
version (PlattformUnittest)
{
private unittest
{
for (uint counter; counter <= 8 * uint.sizeof; ++counter)
{
const value = pow(2, counter) - 1;
const inNetworkOrder = htonl(value);
const p = cast(ubyte*) &inNetworkOrder;
auto networkOrder = NetworkOrder!4(value);
assert(p[0 .. uint.sizeof].toHostOrder() == value);
}
for (ushort counter; counter <= 8 * ushort.sizeof; ++counter)
{
const value = cast(ushort) (pow(2, counter) - 1);
const inNetworkOrder = htons(value);
const p = cast(ubyte*) &inNetworkOrder;
auto networkOrder = NetworkOrder!2(value);
assert(p[0 .. ushort.sizeof].toHostOrder() == value);
}
}
}

1421
source/tanya/net/ip.d Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
/**
* Network programming.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2022.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
@ -14,5 +14,7 @@
*/
module tanya.net;
public import tanya.net.iface;
public import tanya.net.inet;
public import tanya.net.ip;
public import tanya.net.uri;

View File

@ -5,7 +5,7 @@
/**
* URL parser.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
@ -14,9 +14,9 @@
*/
module tanya.net.uri;
import std.ascii : isAlphaNum, isDigit;
import std.uni : isAlpha, isNumber;
import tanya.memory;
import std.ascii;
import tanya.conv;
import tanya.memory.allocator;
/**
* Thrown if an invalid URI was specified.
@ -33,7 +33,7 @@ final class URIException : Exception
this(string msg,
string file = __FILE__,
size_t line = __LINE__,
Throwable next = null) @nogc @safe pure nothrow
Throwable next = null) @nogc nothrow pure @safe
{
super(msg, file, line, next);
}
@ -78,7 +78,7 @@ struct URL
*
* Throws: $(D_PSYMBOL URIException) if the URL is malformed.
*/
this(const char[] source) pure @nogc
this(const char[] source) @nogc pure
{
ptrdiff_t pos = -1, endPos = source.length, start;
@ -148,18 +148,15 @@ struct URL
goto ParsePath;
}
}
else
else if (!parsePort(source[pos .. $]))
{
// Schemas like mailto: and zlib: may not have any slash after
// them.
if (!parsePort(source[pos .. $]))
{
this.scheme = source[0 .. pos];
start = pos + 1;
goto ParsePath;
}
}
}
else if (pos == 0 && parsePort(source[pos .. $]))
{
// An URL shouldn't begin with a port number.
@ -199,8 +196,8 @@ struct URL
this.pass = source[start + i + 1 .. pos];
}
}
else if (!c.isAlpha &&
!c.isNumber &&
else if (!c.isAlpha() &&
!c.isDigit() &&
c != '!' &&
c != ';' &&
c != '=' &&
@ -301,23 +298,13 @@ struct URL
*
* Returns: Whether the port could be found.
*/
private bool parsePort(const char[] port) pure nothrow @safe @nogc
private bool parsePort(const(char)[] port) @nogc nothrow pure @safe
{
ptrdiff_t i = 1;
float lPort = 0;
for (; i < port.length && port[i].isDigit() && i <= 6; ++i)
auto unparsed = port[1 .. $];
auto parsed = readIntegral!ushort(unparsed);
if (unparsed.length == 0 || unparsed[0] == '/')
{
lPort += (port[i] - '0') / cast(float) (10 ^^ (i - 1));
}
if (i != 1 && (i == port.length || port[i] == '/'))
{
lPort *= 10 ^^ (i - 2);
if (lPort > ushort.max)
{
return false;
}
this.port = cast(ushort) lPort;
this.port = parsed;
return true;
}
return false;
@ -325,7 +312,7 @@ struct URL
}
///
@nogc unittest
@nogc pure @system unittest
{
auto u = URL("example.org");
assert(u.path == "example.org");
@ -378,157 +365,6 @@ struct URL
assert(u.fragment == "fragment");
}
private @nogc unittest
{
auto u = URL("127.0.0.1");
assert(u.path == "127.0.0.1");
u = URL("http://127.0.0.1");
assert(u.scheme == "http");
assert(u.host == "127.0.0.1");
u = URL("http://127.0.0.1:9000");
assert(u.scheme == "http");
assert(u.host == "127.0.0.1");
assert(u.port == 9000);
u = URL("127.0.0.1:80");
assert(u.host == "127.0.0.1");
assert(u.port == 80);
assert(u.path is null);
u = URL("//example.net");
assert(u.host == "example.net");
assert(u.scheme is null);
u = URL("//example.net?q=before:after");
assert(u.host == "example.net");
assert(u.query == "q=before:after");
u = URL("localhost:8080");
assert(u.host == "localhost");
assert(u.port == 8080);
assert(u.path is null);
u = URL("ftp:");
assert(u.scheme == "ftp");
u = URL("file:///C:\\Users");
assert(u.scheme == "file");
assert(u.path == "C:\\Users");
u = URL("localhost:66000");
assert(u.scheme == "localhost");
assert(u.path == "66000");
u = URL("file:///home/");
assert(u.scheme == "file");
assert(u.path == "/home/");
u = URL("file:///home/?q=asdf");
assert(u.scheme == "file");
assert(u.path == "/home/");
assert(u.query == "q=asdf");
u = URL("http://secret@example.org");
assert(u.scheme == "http");
assert(u.host == "example.org");
assert(u.user == "secret");
u = URL("h_tp://:80");
assert(u.path == "h_tp://:80");
assert(u.port == 0);
u = URL("zlib:/home/user/file.gz");
assert(u.scheme == "zlib");
assert(u.path == "/home/user/file.gz");
u = URL("h_tp:asdf");
assert(u.path == "h_tp:asdf");
}
private @nogc unittest
{
URIException exception;
try
{
auto u = URL("http://:80");
}
catch (URIException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
URIException exception;
try
{
auto u = URL(":80");
}
catch (URIException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
URIException exception;
try
{
auto u = URL("http://user1:pass1@user2:pass2@example.org");
}
catch (URIException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
URIException exception;
try
{
auto u = URL("http://blah.com:port");
}
catch (URIException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
URIException exception;
try
{
auto u = URL("http://blah.com:66000");
}
catch (URIException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
// Issue 254: https://issues.caraus.io/issues/254.
private @system @nogc unittest
{
auto u = URL("ftp://");
assert(u.scheme == "ftp");
}
/**
* Attempts to parse an URL from a string and returns the specified component
* of the URL or $(D_PSYMBOL URL) if no component is specified.
@ -554,14 +390,14 @@ if (T == "scheme"
return mixin("ret." ~ T);
}
/// Ditto.
URL parseURL(const char[] source) @nogc
/// ditto
URL parseURL(const char[] source) @nogc pure
{
return URL(source);
}
///
@nogc unittest
@nogc pure @system unittest
{
auto u = parseURL("http://example.org:5326");
assert(u.scheme == parseURL!"scheme"("http://example.org:5326"));

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,208 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Range adapters transform some data structures into ranges.
*
* Copyright: Eugene Wissner 2018-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/range/adapter.d,
* tanya/range/adapter.d)
*/
module tanya.range.adapter;
import tanya.algorithm.mutation;
import tanya.memory.lifetime;
import tanya.meta.trait;
import tanya.range;
private mixin template InserterCtor()
{
private Container* container;
private this(return scope ref Container container) @trusted
{
this.container = &container;
}
}
/**
* If $(D_PARAM container) is a container with `insertBack`-support,
* $(D_PSYMBOL backInserter) returns an output range that puts the elements
* into the container with `insertBack`.
*
* The resulting output range supports all types `insertBack` supports.
*
* The range keeps a reference to the container passed to it, it doesn't use
* any other storage. So there is no method to get the written data out of the
* range - the container passed to $(D_PSYMBOL backInserter) contains that data
* and can be used directly after all operations on the output range are
* completed. It also means that the result range is not allowed to outlive its
* container.
*
* Params:
* Container = Container type.
* container = Container used as an output range.
*
* Returns: `insertBack`-based output range.
*/
auto backInserter(Container)(return scope ref Container container)
if (hasMember!(Container, "insertBack"))
{
static struct Inserter
{
void opCall(T)(auto ref T data)
{
this.container.insertBack(forward!data);
}
mixin InserterCtor;
}
return Inserter(container);
}
///
@nogc nothrow pure @safe unittest
{
static struct Container
{
int element;
void insertBack(int element)
{
this.element = element;
}
}
Container container;
backInserter(container)(5);
assert(container.element == 5);
}
/**
* If $(D_PARAM container) is a container with `insertFront`-support,
* $(D_PSYMBOL frontInserter) returns an output range that puts the elements
* into the container with `insertFront`.
*
* The resulting output range supports all types `insertFront` supports.
*
* The range keeps a reference to the container passed to it, it doesn't use
* any other storage. So there is no method to get the written data out of the
* range - the container passed to $(D_PSYMBOL frontInserter) contains that data
* and can be used directly after all operations on the output range are
* completed. It also means that the result range is not allowed to outlive its
* container.
*
* Params:
* Container = Container type.
* container = Container used as an output range.
*
* Returns: `insertFront`-based output range.
*/
auto frontInserter(Container)(return scope ref Container container)
if (hasMember!(Container, "insertFront"))
{
static struct Inserter
{
void opCall(T)(auto ref T data)
{
this.container.insertFront(forward!data);
}
mixin InserterCtor;
}
return Inserter(container);
}
///
@nogc nothrow pure @safe unittest
{
static struct Container
{
int element;
void insertFront(int element)
{
this.element = element;
}
}
Container container;
frontInserter(container)(5);
assert(container.element == 5);
}
/**
* $(D_PSYMBOL arrayInserter) makes an output range out of an array.
*
* The returned output range accepts single values as well as input ranges that
* can be copied into the target array.
*
* Params:
* Array = Array type.
* array = Array.
*
* Returns: An output range writing into $(D_PARAM array).
*/
auto arrayInserter(Array)(return scope ref Array array)
if (isArray!Array)
{
static if (is(Array ArrayT : ArrayT[size], size_t size))
{
alias E = ArrayT;
}
else
{
alias E = ElementType!Array;
}
static struct ArrayInserter
{
private E[] data;
private this(return scope ref Array data) @trusted
{
this.data = data[];
}
void opCall(T)(auto ref T data)
if (is(T : E))
in
{
assert(!this.data.empty);
}
do
{
put(this.data, data);
}
void opCall(R)(auto ref R data)
if (isInputRange!R && isOutputRange!(E[], ElementType!R))
{
this.data = copy(data, this.data);
}
}
return ArrayInserter(array);
}
///
@nogc nothrow pure @safe unittest
{
int[1] array;
arrayInserter(array)(5);
assert(array[0] == 5);
}
///
@nogc nothrow pure @safe unittest
{
char[1] array;
alias Actual = typeof(arrayInserter(array));
static assert(isOutputRange!(Actual, char));
static assert(isOutputRange!(Actual, char[]));
}

220
source/tanya/range/array.d Normal file
View File

@ -0,0 +1,220 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* $(D_PSYMBOL tanya.range.array) implements range primitives for built-in arrays.
*
* This module is a submodule of
* $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/range/package.d, tanya.range).
*
* After importing of
* $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/range/array.d, tanya/range/array.d)
* built-in arrays can act as bidirectional ranges. For that to work the module
* defines a set of functions that accept a built-in array of any type as their
* first argument, so thanks to UFCS (Uniform Function Call Syntax) they can be
* called as if they were array member functions. For example the arrays the
* `.length`-property, but no `.empty` property. So here can be find the
* $(D_PSYMBOL empty) function. Since $(D_INLINECODE empty(array)) and
* $(D_INLINECODE array.empty) are equal for the arrays, arrays get a faked
* property `empty`.
*
* The functions in this module don't change array elements or its underlying
* storage, but some functions alter the slice. Each array maintains a pointer
* to its data and the length and there can be several pointers which point to
* the same data. Array pointer can be advanced and the length can be reduced
* without changing the underlying storage. So slices offer the possibility to
* have multiple views into the same array, point to different positions inside
* it.
*
* Strings ($(D_INLINECODE char[]), (D_INLINECODE wchar[]) and
* (D_INLINECODE dchar[])) are treated as any other normal array, they aren't
* auto-decoded.
*
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/range/array.d,
* tanya/range/array.d)
*/
module tanya.range.array;
/**
* Returns the first element of the $(D_PARAM array).
*
* The element is returned by reference, so $(D_PSYMBOL front) can be also used
* to change the first element of $(D_PARAM array) if it is mutable.
*
* Params:
* T = Element type of $(D_PARAM array).
* array = Built-in array.
*
* Returns: First element.
*
* Precondition: $(D_INLINECODE array.length > 0).
*/
@property ref inout(T) front(T)(return scope inout(T)[] array)
in
{
assert(array.length > 0);
}
do
{
return array[0];
}
///
@nogc nothrow pure @safe unittest
{
string s = "Wenn die Wunde nicht mehr wehtut, schmerzt die Narbe";
static assert(is(typeof(s.front) == immutable char));
assert(s.front == 'W');
wstring w = "Волны несутся, гремя и сверкая";
static assert(is(typeof(w.front) == immutable wchar));
assert(w.front == 'В');
dstring d = "Для писателя память - это почти все";
static assert(is(typeof(d.front) == immutable dchar));
assert(d.front == 'Д');
}
/**
* Returns the last element of the $(D_PARAM array).
*
* The element is returned by reference, so $(D_PSYMBOL back) can be also used
* to change the last element of $(D_PARAM array) if it is mutable.
*
* Params:
* T = Element type of $(D_PARAM array).
* array = Built-in array.
*
* Returns: Last element.
*
* Precondition: $(D_INLINECODE array.length > 0).
*/
@property ref inout(T) back(T)(return scope inout(T)[] array)
in
{
assert(array.length > 0);
}
do
{
return array[$ - 1];
}
///
@nogc nothrow pure @safe unittest
{
string s = "Brecht";
static assert(is(typeof(s.back) == immutable char));
assert(s.back == 't');
wstring w = "Тютчев";
static assert(is(typeof(w.back) == immutable wchar));
assert(w.back == 'в');
dstring d = "Паустовский";
static assert(is(typeof(d.back) == immutable dchar));
assert(d.back == 'й');
}
/**
* $(D_PSYMBOL popFront) and $(D_PSYMBOL popBack) advance the $(D_PARAM array)
* and remove one element from its back, respectively.
*
* $(D_PSYMBOL popFront) and $(D_PSYMBOL popBack) don't alter the array
* storage, they only narrow the view into the array.
*
* Params:
* T = Element type of $(D_PARAM array).
* array = Built-in array.
*
* Precondition: $(D_INLINECODE array.length > 0).
*/
void popFront(T)(ref inout(T)[] array)
in
{
assert(array.length > 0);
}
do
{
array = array[1 .. $];
}
/// ditto
void popBack(T)(ref inout(T)[] array)
in
{
assert(array.length > 0);
}
do
{
array = array[0 .. $ - 1];
}
///
@nogc nothrow pure @safe unittest
{
wstring array = "Der finstere Ozean der Metaphysik. Nietzsche";
array.popFront();
assert(array.length == 43);
assert(array.front == 'e');
array.popBack();
assert(array.length == 42);
assert(array.back == 'h');
}
/**
* Tests whether $(D_PARAM array) is empty.
*
* Params:
* T = Element type of $(D_PARAM array).
* array = Built-in array.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM array) has no elements,
* $(D_KEYWORD false) otherwise.
*/
@property bool empty(T)(scope const T[] array)
{
return array.length == 0;
}
///
@nogc nothrow pure @safe unittest
{
int[1] array;
assert(!array.empty);
assert(array[1 .. 1].empty);
}
/**
* Returns a copy of the slice $(D_PARAM array).
*
* $(D_PSYMBOL save) doesn't copy the array itself, but only the data pointer
* and the length.
*
* Params:
* T = Element type of $(D_PARAM array).
* array = Built-in array.
*
* Returns: A copy of the slice $(D_PARAM array).
*/
@property inout(T)[] save(T)(return scope inout(T)[] array)
{
return array;
}
///
@nogc nothrow pure @safe unittest
{
ubyte[8] array;
auto slice = array.save;
assert(slice.length == array.length);
slice.popFront();
assert(slice.length < array.length);
}

View File

@ -0,0 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This package contains generic functions and templates to be used with D
* ranges.
*
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/range/package.d,
* tanya/range/package.d)
*/
module tanya.range;
public import tanya.range.adapter;
public import tanya.range.array;
public import tanya.range.primitive;

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@
* This module contains templates that allow to build new types from the
* available ones.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
@ -17,14 +17,16 @@
*/
module tanya.typecons;
import std.meta;
import tanya.format;
import tanya.memory.lifetime;
import tanya.meta.metafunction;
import tanya.meta.trait;
/**
* $(D_PSYMBOL Pair) can store two heterogeneous objects.
* $(D_PSYMBOL Tuple) can store two or more heterogeneous objects.
*
* The objects can by accessed by index as $(D_INLINECODE obj[0]) and
* $(D_INLINECODE obj[1]) or by optional names (e.g.
* $(D_INLINECODE obj.first)).
* The objects can by accessed by index as `obj[0]` and `obj[1]` or by optional
* names (e.g. `obj.first`).
*
* $(D_PARAM Specs) contains a list of object types and names. First
* comes the object type, then an optional string containing the name.
@ -33,10 +35,12 @@ import std.meta;
*
* Params:
* Specs = Field types and names.
*
* See_Also: $(D_PSYMBOL tuple).
*/
template Pair(Specs...)
template Tuple(Specs...)
{
template parseSpecs(int fieldCount, Specs...)
template parseSpecs(size_t fieldCount, Specs...)
{
static if (Specs.length == 0)
{
@ -47,13 +51,13 @@ template Pair(Specs...)
static if (is(typeof(Specs[1]) == string))
{
alias parseSpecs
= AliasSeq!(Specs[0],
= AliasSeq!(Pack!(Specs[0], Specs[1]),
parseSpecs!(fieldCount + 1, Specs[2 .. $]));
}
else
{
alias parseSpecs
= AliasSeq!(Specs[0],
= AliasSeq!(Pack!(Specs[0]),
parseSpecs!(fieldCount + 1, Specs[1 .. $]));
}
}
@ -63,28 +67,39 @@ template Pair(Specs...)
}
}
struct Pair
alias ChooseType(alias T) = T.Seq[0];
alias ParsedSpecs = parseSpecs!(0, Specs);
static assert(ParsedSpecs.length > 1, "Invalid argument count");
private string formatAliases(size_t n, Specs...)()
{
static if (Specs.length == 0)
{
return "";
}
else
{
string fieldAlias;
static if (Specs[0].length == 2)
{
char[21] buffer;
fieldAlias = "alias " ~ Specs[0][1] ~ " = expand["
~ integral2String(n, buffer).idup ~ "];";
}
return fieldAlias ~ formatAliases!(n + 1, Specs[1 .. $])();
}
}
struct Tuple
{
/// Field types.
alias Types = parseSpecs!(0, Specs);
static assert(Types.length == 2, "Invalid argument count.");
alias Types = Map!(ChooseType, ParsedSpecs);
// Create field aliases.
static if (is(typeof(Specs[1]) == string))
{
mixin("alias " ~ Specs[1] ~ " = expand[0];");
}
static if (is(typeof(Specs[2]) == string))
{
mixin("alias " ~ Specs[2] ~ " = expand[1];");
}
else static if (is(typeof(Specs[3]) == string))
{
mixin("alias " ~ Specs[3] ~ " = expand[1];");
}
mixin(formatAliases!(0, ParsedSpecs[0 .. $])());
/// Represents the values of the $(D_PSYMBOL Pair) as a list of values.
/// Represents the values of the $(D_PSYMBOL Tuple) as a list of values.
Types expand;
alias expand this;
@ -92,18 +107,321 @@ template Pair(Specs...)
}
///
unittest
@nogc nothrow pure @safe unittest
{
static assert(is(Pair!(int, int)));
static assert(!is(Pair!(int, 5)));
static assert(is(Pair!(int, "first", int)));
static assert(is(Pair!(int, "first", int, "second")));
static assert(is(Pair!(int, "first", int)));
static assert(is(Pair!(int, int, "second")));
static assert(!is(Pair!("first", int, "second", int)));
static assert(!is(Pair!(int, int, int)));
static assert(!is(Pair!(int, "first")));
auto pair = Tuple!(int, "first", string, "second")(1, "second");
assert(pair.first == 1);
assert(pair[0] == 1);
assert(pair.second == "second");
assert(pair[1] == "second");
}
/**
* Creates a new $(D_PSYMBOL Tuple).
*
* Params:
* Names = Field names.
*
* See_Also: $(D_PSYMBOL Tuple).
*/
template tuple(Names...)
{
/**
* Creates a new $(D_PSYMBOL Tuple).
*
* Params:
* Args = Field types.
* args = Field values.
*
* Returns: Newly created $(D_PSYMBOL Tuple).
*/
auto tuple(Args...)(auto ref Args args)
if (Args.length >= Names.length && isTypeTuple!Args)
{
alias Zipped = ZipWith!(AliasSeq, Pack!Args, Pack!Names);
alias Nameless = Args[Names.length .. $];
return Tuple!(Zipped, Nameless)(forward!args);
}
}
///
@nogc nothrow pure @safe unittest
{
auto t = tuple!("one", "two")(20, 5);
assert(t.one == 20);
assert(t.two == 5);
}
/**
* Type that can hold one of the types listed as its template parameters.
*
* $(D_PSYMBOL Variant) is a type similar to $(D_KEYWORD union), but
* $(D_PSYMBOL Variant) keeps track of the actually used type and throws an
* assertion error when trying to access an invalid type at runtime.
*
* Params:
* Specs = Types this $(D_SPYBMOL Variant) can hold.
*/
template Variant(Specs...)
if (isTypeTuple!Specs && NoDuplicates!Specs.length == Specs.length)
{
union AlignedUnion(Args...)
{
static if (Args.length > 0)
{
Args[0] value;
}
static if (Args.length > 1)
{
AlignedUnion!(Args[1 .. $]) rest;
}
}
private struct VariantAccessorInfo
{
string accessor;
ptrdiff_t tag;
}
template accessor(T, Union)
{
enum VariantAccessorInfo info = accessorImpl!(T, Union, 1);
enum accessor = VariantAccessorInfo("this.values" ~ info.accessor, info.tag);
}
template accessorImpl(T, Union, size_t tag)
{
static if (is(T == typeof(Union.value)))
{
enum accessorImpl = VariantAccessorInfo(".value", tag);
}
else
{
enum VariantAccessorInfo info = accessorImpl!(T, typeof(Union.rest), tag + 1);
enum accessorImpl = VariantAccessorInfo(".rest" ~ info.accessor, info.tag);
}
}
struct Variant
{
/// Types can be present in this $(D_PSYMBOL Variant).
alias Types = Specs;
private ptrdiff_t tag = -1;
private AlignedUnion!Types values;
/**
* Constructs this $(D_PSYMBOL Variant) with one of the types supported
* in it.
*
* Params:
* T = Type of the initial value.
* value = Initial value.
*/
this(T)(ref T value)
if (canFind!(T, Types))
{
copyAssign!T(value);
}
/// ditto
this(T)(T value)
if (canFind!(T, Types))
{
moveAssign!T(value);
}
~this()
{
reset();
}
this(this)
{
alias pred(U) = hasElaborateCopyConstructor!(U.Seq[1]);
static foreach (Type; Filter!(pred, Enumerate!Types))
{
if (this.tag == Type.Seq[0])
{
get!(Type.Seq[1]).__postblit();
}
}
}
/**
* Tells whether this $(D_PSYMBOL Variant) is initialized.
*
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Variant) contains a
* value, $(D_KEYWORD false) otherwise.
*/
bool hasValue() const
{
return this.tag != -1;
}
/**
* Tells whether this $(D_PSYMBOL Variant) holds currently a value of
* type $(D_PARAM T).
*
* Params:
* T = Examined type.
*
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Variant) currently
* contains a value of type $(D_PARAM T), $(D_KEYWORD false)
* otherwise.
*/
bool peek(T)() const
if (canFind!(T, Types))
{
return this.tag == staticIndexOf!(T, Types);
}
/**
* Returns the underlying value, assuming it is of the type $(D_PARAM T).
*
* Params:
* T = Type of the value should be returned.
*
* Returns: The underyling value.
*
* Precondition: The $(D_PSYMBOL Variant) has a value.
*
* See_Also: $(D_PSYMBOL peek), $(D_PSYMBOL hasValue).
*/
ref inout(T) get(T)() inout
if (canFind!(T, Types))
in
{
assert(this.tag == staticIndexOf!(T, Types), "Variant isn't initialized");
}
do
{
mixin("return " ~ accessor!(T, AlignedUnion!Types).accessor ~ ";");
}
/**
* Reassigns the value.
*
* Params:
* T = Type of the new value
* that = New value.
*
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) opAssign(T)(T that)
if (canFind!(T, Types))
{
reset();
return moveAssign!T(that);
}
/// ditto
ref typeof(this) opAssign(T)(ref T that)
if (canFind!(T, Types))
{
reset();
return copyAssign!T(that);
}
private ref typeof(this) moveAssign(T)(ref T that) @trusted
{
this.tag = staticIndexOf!(T, Types);
enum string accessorMixin = accessor!(T, AlignedUnion!Types).accessor;
moveEmplace(that, mixin(accessorMixin));
return this;
}
private ref typeof(this) copyAssign(T)(ref T that) return
{
this.tag = staticIndexOf!(T, Types);
enum string accessorMixin = accessor!(T, AlignedUnion!Types).accessor;
emplace!T((() @trusted => (&mixin(accessorMixin))[0 .. 1])(), that);
return this;
}
private void reset()
{
alias pred(U) = hasElaborateDestructor!(U.Seq[1]);
static foreach (Type; Filter!(pred, Enumerate!Types))
{
if (this.tag == Type.Seq[0])
{
destroy(get!(Type.Seq[1]));
}
}
}
/**
* Returns $(D_PSYMBOL TypeInfo) corresponding to the current type.
*
* If this $(D_PSYMBOL Variant) isn't initialized, returns
* $(D_KEYWORD null).
*
* Returns: $(D_PSYMBOL TypeInfo) of the current type.
*/
@property TypeInfo type()
{
static foreach (i, Type; Types)
{
if (this.tag == i)
{
return typeid(Type);
}
}
return null;
}
/**
* Compares this $(D_PSYMBOL Variant) with another one with the same
* specification for equality.
*
* $(UL
* $(LI If both hold values of the same type, these values are
* compared.)
* $(LI If they hold values of different types, then the
* $(D_PSYMBOL Variant)s aren't equal.)
* $(LI If only one of them is initialized but another one not, they
* aren't equal.)
* $(LI If neither of them is initialized, they are equal.)
* )
*
* Params:
* that = The $(D_PSYMBOL Variant) to compare with.
*
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Variant) is equal to
* $(D_PARAM that), $(D_KEYWORD false) otherwise.
*/
bool opEquals()(auto ref inout(Variant) that) inout
{
if (this.tag != that.tag)
{
return false;
}
static foreach (i, Type; Types)
{
if (this.tag == i)
{
return get!Type == that.get!Type;
}
}
return true;
}
}
}
///
@nogc nothrow pure @safe unittest
{
Variant!(int, double) variant = 5;
assert(variant.peek!int);
assert(variant.get!int == 5);
variant = 5.4;
assert(!variant.peek!int);
assert(variant.get!double == 5.4);
}

17
test/dub.json Normal file
View File

@ -0,0 +1,17 @@
{
"name": "test",
"description": "Test suite for unittest-blocks",
"targetType": "library",
"dependencies": {
"tanya:middle": "*"
},
"sourcePaths": [
"."
],
"importPaths": [
"."
],
"dflags-dmd": ["-dip1000"]
}

105
test/tanya/test/assertion.d Normal file
View File

@ -0,0 +1,105 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Additional assertions.
*
* This module provides functions that assert whether a given expression
* satisfies some complex condition, that can't be tested with
* $(D_KEYWORD assert) in a single line. Internally all the functions
* just evaluate the expression and call $(D_KEYWORD assert).
*
* The functions can cause segmentation fault if the module is compiled
* in production mode and the condition fails.
*
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/test/assertion.d,
* tanya/test/assertion.d)
*/
module tanya.test.assertion;
import tanya.memory.allocator;
import tanya.meta.trait;
/**
* Asserts whether the function $(D_PARAM expr) throws an exception of type
* $(D_PARAM E). If it does, the exception is catched and properly destroyed.
* If it doesn't, an assertion error is thrown. If the exception doesn't match
* $(D_PARAM E) type, it isn't catched and escapes.
*
* Params:
* E = Expected exception type.
* T = Throwing function type.
* Args = Argument types of the throwing function.
* expr = Throwing function.
* args = Arguments for $(D_PARAM expr).
*/
void assertThrown(E : Exception, T, Args...)(T expr, auto ref Args args)
if (isSomeFunction!T)
{
try
{
cast(void) expr(args);
assert(false, "Expected exception not thrown");
}
catch (E exception)
{
defaultAllocator.dispose(exception);
}
}
///
@nogc nothrow pure @safe unittest
{
// If you want to test that an expression throws, you can wrap it into an
// arrow function.
static struct CtorThrows
{
this(int i) @nogc pure @safe
{
throw defaultAllocator.make!Exception();
}
}
assertThrown!Exception(() => CtorThrows(8));
}
/**
* Asserts that the function $(D_PARAM expr) doesn't throw.
*
* If it does, the thrown exception is catched, properly destroyed and an
* assertion error is thrown instead.
*
* Params:
* T = Tested function type.
* Args = Argument types of $(D_PARAM expr).
* expr = Tested function.
* args = Arguments for $(D_PARAM expr).
*/
void assertNotThrown(T, Args...)(T expr, auto ref Args args)
if (isSomeFunction!T)
{
try
{
cast(void) expr(args);
}
catch (Exception exception)
{
defaultAllocator.dispose(exception);
assert(false, "Unexpected exception thrown");
}
}
///
@nogc nothrow pure @safe unittest
{
// If you want to test that an expression doesn't throw, you can wrap it
// into an arrow function.
static struct S
{
}
assertNotThrown(() => S());
}

18
test/tanya/test/package.d Normal file
View File

@ -0,0 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Test suite for $(D_KEYWORD unittest)-blocks.
*
* Copyright: Eugene Wissner 2017-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/test/package.d,
* tanya/test/package.d)
*/
module tanya.test;
public import tanya.test.assertion;
public import tanya.test.stub;

397
test/tanya/test/stub.d Normal file
View File

@ -0,0 +1,397 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Range and generic type generators.
*
* Copyright: Eugene Wissner 2018-2020.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/test/stub.d,
* tanya/test/stub.d)
*/
module tanya.test.stub;
/**
* Attribute signalizing that the generated range should contain the given
* number of elements.
*
* $(D_PSYMBOL Count) should be always specified with some value and not as a
* type, so $(D_INLINECODE Count(1)) instead just $(D_INLINECODE Count),
* otherwise you can just omit $(D_PSYMBOL Count) and it will default to 0.
*
* $(D_PSYMBOL Count) doesn't generate `.length` property - use
* $(D_PSYMBOL Length) for that.
*
* If neither $(D_PSYMBOL Length) nor $(D_PSYMBOL Infinite) is given,
* $(D_ILNINECODE Count(0)) is assumed.
*
* This attribute conflicts with $(D_PSYMBOL Infinite) and $(D_PSYMBOL Length).
*/
struct Count
{
/// Original range length.
size_t count = 0;
@disable this();
/**
* Constructs the attribute with the given length.
*
* Params:
* count = Original range length.
*/
this(size_t count) @nogc nothrow pure @safe
{
this.count = count;
}
}
/**
* Attribute signalizing that the generated range should be infinite.
*
* This attribute conflicts with $(D_PSYMBOL Count) and $(D_PSYMBOL Length).
*/
struct Infinite
{
}
/**
* Generates `.length` property for the range.
*
* The length of the range can be specified as a constructor argument,
* otherwise it is 0.
*
* This attribute conflicts with $(D_PSYMBOL Count) and $(D_PSYMBOL Infinite).
*/
struct Length
{
/// Original range length.
size_t length = 0;
}
/**
* Attribute signalizing that the generated range should return values by
* reference.
*
* This atribute affects the return values of `.front`, `.back` and `[]`.
*/
struct WithLvalueElements
{
}
/**
* Generates an input range.
*
* Params:
* E = Element type.
*/
mixin template InputRangeStub(E = int)
{
import tanya.meta.metafunction : Alias;
import tanya.meta.trait : evalUDA, getUDAs, hasUDA;
/*
* Aliases for the attribute lookups to access them faster
*/
private enum bool infinite = hasUDA!(typeof(this), Infinite);
private enum bool withLvalueElements = hasUDA!(typeof(this),
WithLvalueElements);
private alias Count = getUDAs!(typeof(this), .Count);
private alias Length = getUDAs!(typeof(this), .Length);
static if (Count.length != 0)
{
private enum size_t count = Count[0].count;
static assert (!infinite,
"Range cannot have count and be infinite at the same time");
static assert (Length.length == 0,
"Range cannot have count and length at the same time");
}
else static if (Length.length != 0)
{
private enum size_t count = evalUDA!(Length[0]).length;
static assert (!infinite,
"Range cannot have length and be infinite at the same time");
}
else static if (!infinite)
{
private enum size_t count = 0;
}
/*
* Member generation
*/
static if (infinite)
{
enum bool empty = false;
}
else
{
private size_t length_ = count;
@property bool empty() const @nogc nothrow pure @safe
{
return this.length_ == 0;
}
}
static if (withLvalueElements)
{
private E* element; // Pointer to enable range copying in save()
}
void popFront() @nogc nothrow pure @safe
in
{
assert(!empty);
}
do
{
static if (!infinite)
{
--this.length_;
}
}
static if (withLvalueElements)
{
ref E front() @nogc nothrow pure @safe
in
{
assert(!empty);
}
do
{
return *this.element;
}
}
else
{
E front() @nogc nothrow pure @safe
in
{
assert(!empty);
}
do
{
return E.init;
}
}
static if (Length.length != 0)
{
size_t length() const @nogc nothrow pure @safe
{
return this.length_;
}
}
}
/**
* Generates a forward range.
*
* This mixin includes input range primitives as well, but can be combined with
* $(D_PSYMBOL RandomAccessRangeStub).
*
* Params:
* E = Element type.
*/
mixin template ForwardRangeStub(E = int)
{
static if (!is(typeof(this.InputRangeMixin) == void))
{
mixin InputRangeStub!E InputRangeMixin;
}
auto save() @nogc nothrow pure @safe
{
return this;
}
}
/**
* Generates a bidirectional range.
*
* This mixin includes forward range primitives as well, but can be combined with
* $(D_PSYMBOL RandomAccessRangeStub).
*
* Params:
* E = Element type.
*/
mixin template BidirectionalRangeStub(E = int)
{
mixin ForwardRangeStub!E;
void popBack() @nogc nothrow pure @safe
in
{
assert(!empty);
}
do
{
static if (!infinite)
{
--this.length_;
}
}
static if (withLvalueElements)
{
ref E back() @nogc nothrow pure @safe
in
{
assert(!empty);
}
do
{
return *this.element;
}
}
else
{
E back() @nogc nothrow pure @safe
in
{
assert(!empty);
}
do
{
return E.init;
}
}
}
/**
* Generates a random-access range.
*
* This mixin includes input range primitives as well, but can be combined with
* $(D_PSYMBOL ForwardRangeStub) or $(D_PSYMBOL BidirectionalRangeStub).
*
* Note that a random-access range also requires $(D_PSYMBOL Length) or
* $(D_PARAM Infinite) by definition.
*
* Params:
* E = Element type.
*/
mixin template RandomAccessRangeStub(E = int)
{
static if (!is(typeof(this.InputRangeMixin) == void))
{
mixin InputRangeStub!E InputRangeMixin;
}
static if (withLvalueElements)
{
ref E opIndex(size_t) @nogc nothrow pure @safe
{
return *this.element;
}
}
else
{
E opIndex(size_t) @nogc nothrow pure @safe
{
return E.init;
}
}
}
/**
* Struct with a disabled postblit constructor.
*
* $(D_PSYMBOL NonCopyable) can be used as an attribute for
* $(D_PSYMBOL StructStub) or as a standalone type.
*/
struct NonCopyable
{
@disable this(this);
}
/**
* Struct with an elaborate destructor.
*
* $(D_PSYMBOL WithDtor) can be used as an attribute for
* $(D_PSYMBOL StructStub) or as a standalone type.
*
* When used as a standalone object the constructor of $(D_PSYMBOL WithDtor)
* accepts an additional `counter` argument, which is incremented by the
* destructor. $(D_PSYMBOL WithDtor) stores a pointer to the passed variable,
* so the variable can be investigated after the struct isn't available
* anymore.
*/
struct WithDtor
{
size_t* counter;
this(ref size_t counter) @nogc nothrow pure @trusted
{
this.counter = &counter;
}
~this() @nogc nothrow pure @safe
{
if (this.counter !is null)
{
++*this.counter;
}
}
}
/**
* Struct supporting hashing.
*
* $(D_PSYMBOL Hashable) can be used as an attribute for
* $(D_PSYMBOL StructStub) or as a standalone type.
*
* The constructor accepts an additional parameter, which is returned by the
* `toHash()`-function. `0U` is returned if no hash value is given.
*/
struct Hashable
{
size_t hash;
size_t toHash() const @nogc nothrow pure @safe
{
return this.hash;
}
}
/**
* Generates a $(D_KEYWORD struct) with common functionality.
*
* To specify the needed functionality use user-defined attributes on the
* $(D_KEYWORD struct) $(D_PSYMBOL StructStub) is mixed in.
*
* Supported attributes are: $(D_PSYMBOL NonCopyable), $(D_PSYMBOL Hashable),
* $(D_PSYMBOL WithDtor).
*/
mixin template StructStub()
{
import tanya.meta.trait : evalUDA, getUDAs, hasUDA;
static if (hasUDA!(typeof(this), NonCopyable))
{
@disable this(this);
}
private alias Hashable = getUDAs!(typeof(this), .Hashable);
static if (Hashable.length > 0)
{
size_t toHash() const @nogc nothrow pure @safe
{
return evalUDA!(Hashable[0]).hash;
}
}
static if (hasUDA!(typeof(this), WithDtor))
{
~this() @nogc nothrow pure @safe
{
}
}
}

View File

@ -0,0 +1,55 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.algorithm.tests.iteration;
import tanya.algorithm.iteration;
import tanya.range;
import tanya.test.stub;
// Singleton range is bidirectional and random-access
@nogc nothrow pure @safe unittest
{
static assert(isBidirectionalRange!(typeof(singleton('a'))));
static assert(isRandomAccessRange!(typeof(singleton('a'))));
assert({ char a; return isBidirectionalRange!(typeof(singleton(a))); });
assert({ char a; return isRandomAccessRange!(typeof(singleton(a))); });
}
@nogc nothrow pure @safe unittest
{
char a = 'a';
auto single = singleton(a);
assert(single.front == 'a');
assert(single.back == 'a');
assert(single[0] == 'a');
assert(single.length == 1);
assert(!single.empty);
}
// popFront makes SingletonByRef empty
@nogc nothrow pure @safe unittest
{
char a = 'a';
auto single = singleton(a);
single.popFront();
assert(single.empty);
assert(single.length == 0);
assert(single.empty);
}
// popBack makes SingletonByRef empty
@nogc nothrow pure @safe unittest
{
char a = 'b';
auto single = singleton(a);
single.popBack();
assert(single.empty);
assert(single.length == 0);
assert(single.empty);
}

View File

@ -0,0 +1,97 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.algorithm.tests.mutation;
import tanya.algorithm.mutation;
import tanya.range;
import tanya.test.stub;
// Returns advanced target
@nogc nothrow pure @safe unittest
{
int[5] input = [1, 2, 3, 4, 5];
assert(copy(input[3 .. 5], input[]).front == 3);
}
// Copies overlapping arrays
@nogc nothrow pure @safe unittest
{
import std.algorithm.comparison : equal;
int[6] actual = [1, 2, 3, 4, 5, 6];
const int[6] expected = [1, 2, 1, 2, 3, 4];
copy(actual[0 .. 4], actual[2 .. 6]);
assert(equal(actual[], expected[]));
}
@nogc nothrow pure @safe unittest
{
static assert(is(typeof(copy((ubyte[]).init, (ushort[]).init))));
static assert(!is(typeof(copy((ushort[]).init, (ubyte[]).init))));
}
@nogc nothrow pure @safe unittest
{
static struct OutPutRange
{
int value;
void opCall(int value) @nogc nothrow pure @safe
in
{
assert(this.value == 0);
}
do
{
this.value = value;
}
}
int[1] source = [5];
OutPutRange target;
assert(copy(source[], target).value == 5);
}
// [] is called where possible
@nogc nothrow pure @system unittest
{
static struct Slice
{
bool* slicingCalled;
int front() @nogc nothrow pure @safe
{
return 0;
}
void front(int) @nogc nothrow pure @safe
{
}
void popFront() @nogc nothrow pure @safe
{
}
bool empty() @nogc nothrow pure @safe
{
return true;
}
void opIndexAssign(int) @nogc nothrow pure @safe
{
*this.slicingCalled = true;
}
}
bool slicingCalled;
auto range = Slice(&slicingCalled);
fill(range, 0);
assert(slicingCalled);
}
@nogc nothrow pure @safe unittest
{
NonCopyable[] nonCopyable;
initializeAll(nonCopyable);
}

View File

@ -0,0 +1,189 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.container.tests.array;
import std.algorithm.comparison;
import tanya.container.array;
import tanya.memory.allocator;
import tanya.test.stub;
// const arrays return usable ranges
@nogc nothrow pure @safe unittest
{
auto v = const Array!int([1, 2, 4]);
auto r1 = v[];
assert(r1.back == 4);
r1.popBack();
assert(r1.back == 2);
r1.popBack();
assert(r1.back == 1);
r1.popBack();
assert(r1.length == 0);
static assert(!is(typeof(r1[0] = 5)));
static assert(!is(typeof(v[0] = 5)));
const r2 = r1[];
static assert(is(typeof(r2[])));
}
@nogc nothrow pure @safe unittest
{
Array!int v1;
const Array!int v2;
auto r1 = v1[];
auto r2 = v1[];
assert(r1.length == 0);
assert(r2.empty);
assert(r1 == r2);
v1.insertBack([1, 2, 4]);
assert(v1[] == v1);
assert(v2[] == v2);
assert(v2[] != v1);
assert(v1[] != v2);
assert(v1[].equal(v1[]));
assert(v2[].equal(v2[]));
assert(!v1[].equal(v2[]));
}
@nogc nothrow pure @safe unittest
{
struct MutableEqualsStruct
{
bool opEquals(typeof(this) that) @nogc nothrow pure @safe
{
return true;
}
}
struct ConstEqualsStruct
{
bool opEquals(const typeof(this) that) const @nogc nothrow pure @safe
{
return true;
}
}
auto v1 = Array!ConstEqualsStruct();
auto v2 = Array!ConstEqualsStruct();
assert(v1 == v2);
assert(v1[] == v2);
assert(v1 == v2[]);
assert(v1[].equal(v2[]));
auto v3 = const Array!ConstEqualsStruct();
auto v4 = const Array!ConstEqualsStruct();
assert(v3 == v4);
assert(v3[] == v4);
assert(v3 == v4[]);
assert(v3[].equal(v4[]));
auto v7 = Array!MutableEqualsStruct(1, MutableEqualsStruct());
auto v8 = Array!MutableEqualsStruct(1, MutableEqualsStruct());
assert(v7 == v8);
assert(v7[] == v8);
assert(v7 == v8[]);
assert(v7[].equal(v8[]));
}
// Destructor can destroy empty arrays
@nogc nothrow pure @safe unittest
{
auto v = Array!WithDtor();
}
@nogc nothrow pure @safe unittest
{
class A
{
}
A a1, a2;
auto v1 = Array!A([a1, a2]);
static assert(is(Array!(A*)));
}
@nogc nothrow pure @safe unittest
{
auto v = Array!int([5, 15, 8]);
{
size_t i;
foreach (e; v)
{
assert(i != 0 || e == 5);
assert(i != 1 || e == 15);
assert(i != 2 || e == 8);
++i;
}
assert(i == 3);
}
{
size_t i = 3;
foreach_reverse (e; v)
{
--i;
assert(i != 2 || e == 8);
assert(i != 1 || e == 15);
assert(i != 0 || e == 5);
}
assert(i == 0);
}
}
// const constructor tests
@nogc nothrow pure @system unittest
{
auto v1 = const Array!int([1, 2, 3]);
auto v2 = Array!int(v1);
assert(v1.get !is v2.get);
assert(v1 == v2);
auto v3 = const Array!int(Array!int([1, 2, 3]));
assert(v1 == v3);
assert(v3.length == 3);
assert(v3.capacity == 3);
}
@nogc nothrow pure @safe unittest
{
auto v1 = Array!int(defaultAllocator);
}
@nogc nothrow pure @safe unittest
{
Array!int v;
auto r = v[];
assert(r.length == 0);
assert(r.empty);
}
@nogc nothrow pure @safe unittest
{
auto v1 = const Array!int([5, 15, 8]);
Array!int v2;
v2 = v1[0 .. 2];
assert(equal(v1[0 .. 2], v2[]));
}
// Move assignment
@nogc nothrow pure @safe unittest
{
Array!int v1;
v1 = Array!int([5, 15, 8]);
}
// Postblit is safe
@nogc nothrow pure @safe unittest
{
auto array = Array!int(3);
void func(Array!int arg)
{
assert(arg.capacity == 3);
}
func(array);
}

View File

@ -0,0 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.container.tests.buffer;
import tanya.container.buffer;
@nogc nothrow pure @safe unittest
{
static assert(is(ReadBuffer!int));
}
@nogc nothrow pure @safe unittest
{
static assert(is(typeof(WriteBuffer!int(5))));
}

View File

@ -0,0 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.container.tests.entry;
import tanya.container.entry;
import tanya.test.stub;
// Can be constructed with non-copyable key/values
@nogc nothrow pure @safe unittest
{
static assert(is(Bucket!NonCopyable));
static assert(is(Bucket!(NonCopyable, NonCopyable)));
static assert(is(HashArray!((ref NonCopyable) => 0U, NonCopyable)));
static assert(is(HashArray!((ref NonCopyable) => 0U, NonCopyable, NonCopyable)));
}

View File

@ -0,0 +1,133 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.container.tests.hashtable;
import tanya.container.hashtable;
import tanya.test.stub;
@nogc nothrow pure @safe unittest
{
import tanya.range.primitive : isForwardRange;
static assert(is(HashTable!(string, int) a));
static assert(is(const HashTable!(string, int)));
static assert(isForwardRange!(HashTable!(string, int).Range));
static assert(is(HashTable!(int, int, (ref const int) => size_t.init)));
static assert(is(HashTable!(int, int, (int) => size_t.init)));
}
// Constructs by reference
@nogc nothrow pure @safe unittest
{
auto hashTable1 = HashTable!(string, int)(7);
auto hashTable2 = HashTable!(string, int)(hashTable1);
assert(hashTable1.length == hashTable2.length);
assert(hashTable1.capacity == hashTable2.capacity);
}
// Constructs by value
@nogc nothrow pure @safe unittest
{
auto hashTable = HashTable!(string, int)(HashTable!(string, int)(7));
assert(hashTable.capacity == 7);
}
// Assigns by reference
@nogc nothrow pure @safe unittest
{
auto hashTable1 = HashTable!(string, int)(7);
HashTable!(string, int) hashTable2;
hashTable1 = hashTable2;
assert(hashTable1.length == hashTable2.length);
assert(hashTable1.capacity == hashTable2.capacity);
}
// Assigns by value
@nogc nothrow pure @safe unittest
{
HashTable!(string, int) hashTable;
hashTable = HashTable!(string, int)(7);
assert(hashTable.capacity == 7);
}
// Postblit copies
@nogc nothrow pure @safe unittest
{
auto hashTable = HashTable!(string, int)(7);
void testFunc(HashTable!(string, int) hashTable)
{
assert(hashTable.capacity == 7);
}
testFunc(hashTable);
}
// Issue 53: https://github.com/caraus-ecms/tanya/issues/53
@nogc nothrow pure @safe unittest
{
{
HashTable!(uint, uint) hashTable;
foreach (uint i; 0 .. 14)
{
hashTable[i + 1] = i;
}
assert(hashTable.length == 14);
}
{
HashTable!(int, int) hashtable;
hashtable[1194250162] = 3;
hashtable[-1131293824] = 6;
hashtable[838100082] = 9;
hashtable.rehash(11);
assert(hashtable[-1131293824] == 6);
}
}
@nogc nothrow pure @safe unittest
{
static struct String
{
bool opEquals(string) const @nogc nothrow pure @safe
{
return true;
}
bool opEquals(ref const string) const @nogc nothrow pure @safe
{
return true;
}
bool opEquals(String) const @nogc nothrow pure @safe
{
return true;
}
bool opEquals(ref const String) const @nogc nothrow pure @safe
{
return true;
}
size_t toHash() const @nogc nothrow pure @safe
{
return 0;
}
}
static assert(is(typeof("asdf" in HashTable!(String, int)())));
static assert(is(typeof(HashTable!(String, int)()["asdf"])));
}
// Can have non-copyable keys and elements
@nogc nothrow pure @safe unittest
{
@NonCopyable @Hashable
static struct S
{
mixin StructStub;
}
static assert(is(HashTable!(S, int)));
static assert(is(HashTable!(int, S)));
static assert(is(HashTable!(S, S)));
}

View File

@ -0,0 +1,120 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.container.tests.list;
import tanya.container.list;
import tanya.test.stub;
@nogc nothrow pure @safe unittest
{
interface Stuff
{
}
static assert(is(SList!Stuff));
}
@nogc nothrow pure @safe unittest
{
auto l = SList!int(0, 0);
assert(l.empty);
}
// foreach called using opIndex().
@nogc nothrow pure @safe unittest
{
SList!int l;
size_t i;
l.insertFront(5);
l.insertFront(4);
l.insertFront(9);
foreach (e; l)
{
assert(i != 0 || e == 9);
assert(i != 1 || e == 4);
assert(i != 2 || e == 5);
++i;
}
}
@nogc nothrow pure @safe unittest
{
auto l1 = SList!int();
auto l2 = SList!int([9, 4]);
l1 = l2[];
assert(l1 == l2);
}
@nogc nothrow pure @safe unittest
{
class A
{
}
static assert(is(SList!(A*)));
static assert(is(DList!(A*)));
}
// Removes all elements
@nogc nothrow pure @safe unittest
{
auto l = DList!int([5]);
assert(l.remove(l[]).empty);
}
@nogc nothrow pure @safe unittest
{
auto l1 = DList!int([5, 234, 30, 1]);
auto l2 = DList!int([5, 1]);
auto r = l1[];
r.popFront();
r.popBack();
assert(r.front == 234);
assert(r.back == 30);
assert(!l1.remove(r).empty);
assert(l1 == l2);
}
@nogc nothrow pure @safe unittest
{
auto l = DList!int(0, 0);
assert(l.empty);
}
@nogc nothrow pure @safe unittest
{
DList!int l;
l.insertAfter(l[], 234);
assert(l.front == 234);
assert(l.back == 234);
}
@nogc nothrow pure @safe unittest
{
auto l1 = DList!int();
auto l2 = DList!int([9, 4]);
l1 = l2[];
assert(l1 == l2);
}
// Sets the new head
@nogc nothrow pure @safe unittest
{
auto l1 = DList!int([5, 234, 30, 1]);
auto l2 = DList!int([1]);
auto r = l1[];
r.popBack();
assert(!l1.remove(r).empty);
assert(l1 == l2);
}
// Can have non-copyable elements
@nogc nothrow pure @safe unittest
{
static assert(is(SList!NonCopyable));
static assert(is(DList!NonCopyable));
}

View File

@ -0,0 +1,155 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.container.tests.set;
import tanya.container.set;
import tanya.memory.allocator;
import tanya.test.stub;
// Basic insertion logic.
@nogc nothrow pure @safe unittest
{
Set!int set;
assert(set.insert(5) == 1);
assert(5 in set);
assert(set.capacity == 3);
assert(set.insert(5) == 0);
assert(5 in set);
assert(set.capacity == 3);
assert(set.insert(9) == 1);
assert(9 in set);
assert(5 in set);
assert(set.capacity == 3);
assert(set.insert(7) == 1);
assert(set.insert(8) == 1);
assert(8 in set);
assert(5 in set);
assert(9 in set);
assert(7 in set);
assert(set.capacity == 7);
assert(set.insert(16) == 1);
assert(16 in set);
assert(set.capacity == 7);
}
// Static checks.
@nogc nothrow pure @safe unittest
{
import tanya.range.primitive;
static assert(isBidirectionalRange!(Set!int.ConstRange));
static assert(isBidirectionalRange!(Set!int.Range));
static assert(!isInfinite!(Set!int.Range));
static assert(!hasLength!(Set!int.Range));
static assert(is(Set!uint));
static assert(is(Set!long));
static assert(is(Set!ulong));
static assert(is(Set!short));
static assert(is(Set!ushort));
static assert(is(Set!bool));
}
@nogc nothrow pure @safe unittest
{
const Set!int set;
assert(set[].empty);
}
@nogc nothrow pure @safe unittest
{
Set!int set;
set.insert(8);
auto r1 = set[];
auto r2 = r1.save();
r1.popFront();
assert(r1.empty);
r2.popBack();
assert(r2.empty);
}
// Initial capacity is 0.
@nogc nothrow pure @safe unittest
{
auto set = Set!int(defaultAllocator);
assert(set.capacity == 0);
}
// Capacity is set to a prime.
@nogc nothrow pure @safe unittest
{
auto set = Set!int(8);
assert(set.capacity == 13);
}
// Constructs by reference
@nogc nothrow pure @safe unittest
{
auto set1 = Set!int(7);
auto set2 = Set!int(set1);
assert(set1.length == set2.length);
assert(set1.capacity == set2.capacity);
}
// Constructs by value
@nogc nothrow pure @safe unittest
{
auto set = Set!int(Set!int(7));
assert(set.capacity == 7);
}
// Assigns by reference
@nogc nothrow pure @safe unittest
{
auto set1 = Set!int(7);
Set!int set2;
set1 = set2;
assert(set1.length == set2.length);
assert(set1.capacity == set2.capacity);
}
// Assigns by value
@nogc nothrow pure @safe unittest
{
Set!int set;
set = Set!int(7);
assert(set.capacity == 7);
}
// Postblit copies
@nogc nothrow pure @safe unittest
{
auto set = Set!int(7);
void testFunc(Set!int set)
{
assert(set.capacity == 7);
}
testFunc(set);
}
// Hasher can take argument by ref
@nogc nothrow pure @safe unittest
{
static assert(is(Set!(int, (const ref x) => cast(size_t) x)));
}
// Can have non-copyable elements
@nogc nothrow pure @safe unittest
{
@NonCopyable @Hashable
static struct S
{
mixin StructStub;
}
static assert(is(Set!S));
}

View File

@ -0,0 +1,121 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.container.tests.string;
import tanya.container.string;
import tanya.test.assertion;
@nogc nothrow pure @safe unittest
{
auto s = String(0, 'K');
assert(s.length == 0);
}
// Allocates enough space for 3-byte character.
@nogc pure @safe unittest
{
String s;
s.insertBack('\u8100');
}
@nogc pure @safe unittest
{
assertThrown!UTFException(() => String(1, cast(dchar) 0xd900));
assertThrown!UTFException(() => String(1, cast(wchar) 0xd900));
}
@nogc nothrow pure @safe unittest
{
auto s1 = String("Buttercup");
auto s2 = String("Cap");
s2[] = s1[6 .. $];
assert(s2 == "cup");
}
@nogc nothrow pure @safe unittest
{
auto s1 = String("Wow");
s1[] = 'a';
assert(s1 == "aaa");
}
@nogc nothrow pure @safe unittest
{
auto s1 = String("ö");
s1[] = "oe";
assert(s1 == "oe");
}
// Postblit works
@nogc nothrow pure @safe unittest
{
void internFunc(String arg)
{
}
void middleFunc(S...)(S args)
{
foreach (arg; args)
{
internFunc(arg);
}
}
void topFunc(String args)
{
middleFunc(args);
}
topFunc(String("asdf"));
}
// Const range produces mutable ranges
@nogc pure @safe unittest
{
auto s = const String("И снизу лед, и сверху - маюсь между.");
{
const constRange = s[];
auto fromConstRange = constRange[];
fromConstRange.popFront();
assert(fromConstRange.front == s[1]);
fromConstRange = constRange[0 .. $];
fromConstRange.popFront();
assert(fromConstRange.front == s[1]);
assert(constRange.get() is s.get());
}
{
const constRange = s.byCodePoint();
auto fromConstRange = constRange[];
fromConstRange.popFront();
assert(fromConstRange.front == ' ');
}
}
// Can pop multibyte characters
@nogc pure @safe unittest
{
auto s = String("\U00024B62\U00002260");
auto range = s.byCodePoint();
range.popFront();
assert(!range.empty);
range.popFront();
assert(range.empty);
range = s.byCodePoint();
range.popFront();
s[$ - 3] = 0xf0;
assertThrown!UTFException(&(range.popFront));
}
// Inserts own char range correctly
@nogc nothrow pure @safe unittest
{
auto s1 = String(`ü`);
String s2;
s2.insertBack(s1[]);
assert(s1 == s2);
}

View File

@ -0,0 +1,501 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.hash.tests.lookup;
import tanya.hash.lookup;
import tanya.test.stub;
// Tests that work for any hash size
@nogc nothrow pure @safe unittest
{
assert(hash(null) == 0);
assert(hash(Hashable()) == 0U);
assert(hash('a') == 'a');
}
static if (size_t.sizeof == 4) @nogc nothrow pure @safe unittest
{
assert(hash(HashRange()) == 0x6222e842U);
assert(hash(ToHashRange()) == 3371162643U);
}
static if (size_t.sizeof == 8) @nogc nothrow pure @safe unittest
{
assert(hash(HashRange()) == 0x08985907b541d342UL);
assert(hash(ToHashRange()) == 2072958611659694473);
}
static if (size_t.sizeof == 4) @nogc nothrow pure @system unittest
{
assert(hash(cast(void*) 0x6e6f6863) == 0x6e6f6863);
}
static if (size_t.sizeof == 8) @nogc nothrow pure @system unittest
{
assert(hash(cast(void*) 0x77206f676e6f6863) == 0x77206f676e6f6863);
}
/*
* These are official FNV-1a test vectors and they are in the public domain.
*/
// FNV-1a 32 bit test vectors
static if (size_t.sizeof == 4) @nogc nothrow pure @safe unittest
{
assert(hash("") == 0x811c9dc5U);
assert(hash("a") == 0xe40c292cU);
assert(hash("b") == 0xe70c2de5U);
assert(hash("c") == 0xe60c2c52U);
assert(hash("d") == 0xe10c2473U);
assert(hash("e") == 0xe00c22e0U);
assert(hash("f") == 0xe30c2799U);
assert(hash("fo") == 0x6222e842U);
assert(hash("foo") == 0xa9f37ed7U);
assert(hash("foob") == 0x3f5076efU);
assert(hash("fooba") == 0x39aaa18aU);
assert(hash("foobar") == 0xbf9cf968U);
assert(hash("\0") == 0x050c5d1fU);
assert(hash("a\0") == 0x2b24d044U);
assert(hash("b\0") == 0x9d2c3f7fU);
assert(hash("c\0") == 0x7729c516U);
assert(hash("d\0") == 0xb91d6109U);
assert(hash("e\0") == 0x931ae6a0U);
assert(hash("f\0") == 0x052255dbU);
assert(hash("fo\0") == 0xbef39fe6U);
assert(hash("foo\0") == 0x6150ac75U);
assert(hash("foob\0") == 0x9aab3a3dU);
assert(hash("fooba\0") == 0x519c4c3eU);
assert(hash("foobar\0") == 0x0c1c9eb8U);
assert(hash("ch") == 0x5f299f4eU);
assert(hash("cho") == 0xef8580f3U);
assert(hash("chon") == 0xac297727U);
assert(hash("chong") == 0x4546b9c0U);
assert(hash("chongo") == 0xbd564e7dU);
assert(hash("chongo ") == 0x6bdd5c67U);
assert(hash("chongo w") == 0xdd77ed30U);
assert(hash("chongo wa") == 0xf4ca9683U);
assert(hash("chongo was") == 0x4aeb9bd0U);
assert(hash("chongo was ") == 0xe0e67ad0U);
assert(hash("chongo was h") == 0xc2d32fa8U);
assert(hash("chongo was he") == 0x7f743fb7U);
assert(hash("chongo was her") == 0x6900631fU);
assert(hash("chongo was here") == 0xc59c990eU);
assert(hash("chongo was here!") == 0x448524fdU);
assert(hash("chongo was here!\n") == 0xd49930d5U);
assert(hash("ch\0") == 0x1c85c7caU);
assert(hash("cho\0") == 0x0229fe89U);
assert(hash("chon\0") == 0x2c469265U);
assert(hash("chong\0") == 0xce566940U);
assert(hash("chongo\0") == 0x8bdd8ec7U);
assert(hash("chongo \0") == 0x34787625U);
assert(hash("chongo w\0") == 0xd3ca6290U);
assert(hash("chongo wa\0") == 0xddeaf039U);
assert(hash("chongo was\0") == 0xc0e64870U);
assert(hash("chongo was \0") == 0xdad35570U);
assert(hash("chongo was h\0") == 0x5a740578U);
assert(hash("chongo was he\0") == 0x5b004d15U);
assert(hash("chongo was her\0") == 0x6a9c09cdU);
assert(hash("chongo was here\0") == 0x2384f10aU);
assert(hash("chongo was here!\0") == 0xda993a47U);
assert(hash("chongo was here!\n\0") == 0x8227df4fU);
assert(hash("cu") == 0x4c298165U);
assert(hash("cur") == 0xfc563735U);
assert(hash("curd") == 0x8cb91483U);
assert(hash("curds") == 0x775bf5d0U);
assert(hash("curds ") == 0xd5c428d0U);
assert(hash("curds a") == 0x34cc0ea3U);
assert(hash("curds an") == 0xea3b4cb7U);
assert(hash("curds and") == 0x8e59f029U);
assert(hash("curds and ") == 0x2094de2bU);
assert(hash("curds and w") == 0xa65a0ad4U);
assert(hash("curds and wh") == 0x9bbee5f4U);
assert(hash("curds and whe") == 0xbe836343U);
assert(hash("curds and whey") == 0x22d5344eU);
assert(hash("curds and whey\n") == 0x19a1470cU);
assert(hash("cu\0") == 0x4a56b1ffU);
assert(hash("cur\0") == 0x70b8e86fU);
assert(hash("curd\0") == 0x0a5b4a39U);
assert(hash("curds\0") == 0xb5c3f670U);
assert(hash("curds \0") == 0x53cc3f70U);
assert(hash("curds a\0") == 0xc03b0a99U);
assert(hash("curds an\0") == 0x7259c415U);
assert(hash("curds and\0") == 0x4095108bU);
assert(hash("curds and \0") == 0x7559bdb1U);
assert(hash("curds and w\0") == 0xb3bf0bbcU);
assert(hash("curds and wh\0") == 0x2183ff1cU);
assert(hash("curds and whe\0") == 0x2bd54279U);
assert(hash("curds and whey\0") == 0x23a156caU);
assert(hash("curds and whey\n\0") == 0x64e2d7e4U);
assert(hash("hi") == 0x683af69aU);
assert(hash("hi\0") == 0xaed2346eU);
assert(hash("hello") == 0x4f9f2cabU);
assert(hash("hello\0") == 0x02935131U);
assert(hash("\xff\x00\x00\x01") == 0xc48fb86dU);
assert(hash("\x01\x00\x00\xff") == 0x2269f369U);
assert(hash("\xff\x00\x00\x02") == 0xc18fb3b4U);
assert(hash("\x02\x00\x00\xff") == 0x50ef1236U);
assert(hash("\xff\x00\x00\x03") == 0xc28fb547U);
assert(hash("\x03\x00\x00\xff") == 0x96c3bf47U);
assert(hash("\xff\x00\x00\x04") == 0xbf8fb08eU);
assert(hash("\x04\x00\x00\xff") == 0xf3e4d49cU);
assert(hash("\x40\x51\x4e\x44") == 0x32179058U);
assert(hash("\x44\x4e\x51\x40") == 0x280bfee6U);
assert(hash("\x40\x51\x4e\x4a") == 0x30178d32U);
assert(hash("\x4a\x4e\x51\x40") == 0x21addaf8U);
assert(hash("\x40\x51\x4e\x54") == 0x4217a988U);
assert(hash("\x54\x4e\x51\x40") == 0x772633d6U);
assert(hash("127.0.0.1") == 0x08a3d11eU);
assert(hash("127.0.0.1\0") == 0xb7e2323aU);
assert(hash("127.0.0.2") == 0x07a3cf8bU);
assert(hash("127.0.0.2\0") == 0x91dfb7d1U);
assert(hash("127.0.0.3") == 0x06a3cdf8U);
assert(hash("127.0.0.3\0") == 0x6bdd3d68U);
assert(hash("64.81.78.68") == 0x1d5636a7U);
assert(hash("64.81.78.68\0") == 0xd5b808e5U);
assert(hash("64.81.78.74") == 0x1353e852U);
assert(hash("64.81.78.74\0") == 0xbf16b916U);
assert(hash("64.81.78.84") == 0xa55b89edU);
assert(hash("64.81.78.84\0") == 0x3c1a2017U);
assert(hash("feedface") == 0x0588b13cU);
assert(hash("feedface\0") == 0xf22f0174U);
assert(hash("feedfacedaffdeed") == 0xe83641e1U);
assert(hash("feedfacedaffdeed\0") == 0x6e69b533U);
assert(hash("feedfacedeadbeef") == 0xf1760448U);
assert(hash("feedfacedeadbeef\0") == 0x64c8bd58U);
assert(hash("line 1\nline 2\nline 3") == 0x97b4ea23U);
assert(hash("chongo <Landon Curt Noll> /\\../\\") == 0x9a4e92e6U);
assert(hash("chongo <Landon Curt Noll> /\\../\\\0") == 0xcfb14012U);
assert(hash("chongo (Landon Curt Noll) /\\../\\") == 0xf01b2511U);
assert(hash("chongo (Landon Curt Noll) /\\../\\\0") == 0x0bbb59c3U);
assert(hash("http://antwrp.gsfc.nasa.gov/apod/astropix.html") == 0xce524afaU);
assert(hash("http://en.wikipedia.org/wiki/Fowler_Noll_Vo_hash") == 0xdd16ef45U);
assert(hash("http://epod.usra.edu/") == 0x60648bb3U);
assert(hash("http://exoplanet.eu/") == 0x7fa4bcfcU);
assert(hash("http://hvo.wr.usgs.gov/cam3/") == 0x5053ae17U);
assert(hash("http://hvo.wr.usgs.gov/cams/HMcam/") == 0xc9302890U);
assert(hash("http://hvo.wr.usgs.gov/kilauea/update/deformation.html") == 0x956ded32U);
assert(hash("http://hvo.wr.usgs.gov/kilauea/update/images.html") == 0x9136db84U);
assert(hash("http://hvo.wr.usgs.gov/kilauea/update/maps.html") == 0xdf9d3323U);
assert(hash("http://hvo.wr.usgs.gov/volcanowatch/current_issue.html") == 0x32bb6cd0U);
assert(hash("http://neo.jpl.nasa.gov/risk/") == 0xc8f8385bU);
assert(hash("http://norvig.com/21-days.html") == 0xeb08bfbaU);
assert(hash("http://primes.utm.edu/curios/home.php") == 0x62cc8e3dU);
assert(hash("http://slashdot.org/") == 0xc3e20f5cU);
assert(hash("http://tux.wr.usgs.gov/Maps/155.25-19.5.html") == 0x39e97f17U);
assert(hash("http://volcano.wr.usgs.gov/kilaueastatus.php") == 0x7837b203U);
assert(hash("http://www.avo.alaska.edu/activity/Redoubt.php") == 0x319e877bU);
assert(hash("http://www.dilbert.com/fast/") == 0xd3e63f89U);
assert(hash("http://www.fourmilab.ch/gravitation/orbits/") == 0x29b50b38U);
assert(hash("http://www.fpoa.net/") == 0x5ed678b8U);
assert(hash("http://www.ioccc.org/index.html") == 0xb0d5b793U);
assert(hash("http://www.isthe.com/cgi-bin/number.cgi") == 0x52450be5U);
assert(hash("http://www.isthe.com/chongo/bio.html") == 0xfa72d767U);
assert(hash("http://www.isthe.com/chongo/index.html") == 0x95066709U);
assert(hash("http://www.isthe.com/chongo/src/calc/lucas-calc") == 0x7f52e123U);
assert(hash("http://www.isthe.com/chongo/tech/astro/venus2004.html") == 0x76966481U);
assert(hash("http://www.isthe.com/chongo/tech/astro/vita.html") == 0x063258b0U);
assert(hash("http://www.isthe.com/chongo/tech/comp/c/expert.html") == 0x2ded6e8aU);
assert(hash("http://www.isthe.com/chongo/tech/comp/calc/index.html") == 0xb07d7c52U);
assert(hash("http://www.isthe.com/chongo/tech/comp/fnv/index.html") == 0xd0c71b71U);
assert(hash("http://www.isthe.com/chongo/tech/math/number/howhigh.html") == 0xf684f1bdU);
assert(hash("http://www.isthe.com/chongo/tech/math/number/number.html") == 0x868ecfa8U);
assert(hash("http://www.isthe.com/chongo/tech/math/prime/mersenne.html") == 0xf794f684U);
assert(hash("http://www.isthe.com/chongo/tech/math/prime/mersenne.html#largest") == 0xd19701c3U);
assert(hash("http://www.lavarnd.org/cgi-bin/corpspeak.cgi") == 0x346e171eU);
assert(hash("http://www.lavarnd.org/cgi-bin/haiku.cgi") == 0x91f8f676U);
assert(hash("http://www.lavarnd.org/cgi-bin/rand-none.cgi") == 0x0bf58848U);
assert(hash("http://www.lavarnd.org/cgi-bin/randdist.cgi") == 0x6317b6d1U);
assert(hash("http://www.lavarnd.org/index.html") == 0xafad4c54U);
assert(hash("http://www.lavarnd.org/what/nist-test.html") == 0x0f25681eU);
assert(hash("http://www.macosxhints.com/") == 0x91b18d49U);
assert(hash("http://www.mellis.com/") == 0x7d61c12eU);
assert(hash("http://www.nature.nps.gov/air/webcams/parks/havoso2alert/havoalert.cfm") == 0x5147d25cU);
assert(hash("http://www.nature.nps.gov/air/webcams/parks/havoso2alert/timelines_24.cfm") == 0x9a8b6805U);
assert(hash("http://www.paulnoll.com/") == 0x4cd2a447U);
assert(hash("http://www.pepysdiary.com/") == 0x1e549b14U);
assert(hash("http://www.sciencenews.org/index/home/activity/view") == 0x2fe1b574U);
assert(hash("http://www.skyandtelescope.com/") == 0xcf0cd31eU);
assert(hash("http://www.sput.nl/~rob/sirius.html") == 0x6c471669U);
assert(hash("http://www.systemexperts.com/") == 0x0e5eef1eU);
assert(hash("http://www.tq-international.com/phpBB3/index.php") == 0x2bed3602U);
assert(hash("http://www.travelquesttours.com/index.htm") == 0xb26249e0U);
assert(hash("http://www.wunderground.com/global/stations/89606.html") == 0x2c9b86a4U);
assert(hash(r10!"21701") == 0xe415e2bbU);
assert(hash(r10!"M21701") == 0x18a98d1dU);
assert(hash(r10!"2^21701-1") == 0xb7df8b7bU);
assert(hash(r10!"\x54\xc5") == 0x241e9075U);
assert(hash(r10!"\xc5\x54") == 0x063f70ddU);
assert(hash(r10!"23209") == 0x0295aed9U);
assert(hash(r10!"M23209") == 0x56a7f781U);
assert(hash(r10!"2^23209-1") == 0x253bc645U);
assert(hash(r10!"\x5a\xa9") == 0x46610921U);
assert(hash(r10!"\xa9\x5a") == 0x7c1577f9U);
assert(hash(r10!"391581216093") == 0x512b2851U);
assert(hash(r10!"391581*2^216093-1") == 0x76823999U);
assert(hash(r10!"\x05\xf9\x9d\x03\x4c\x81") == 0xc0586935U);
assert(hash(r10!"FEDCBA9876543210") == 0xf3415c85U);
assert(hash(r10!"\xfe\xdc\xba\x98\x76\x54\x32\x10") == 0x0ae4ff65U);
assert(hash(r10!"EFCDAB8967452301") == 0x58b79725U);
assert(hash(r10!"\xef\xcd\xab\x89\x67\x45\x23\x01") == 0xdea43aa5U);
assert(hash(r10!"0123456789ABCDEF") == 0x2bb3be35U);
assert(hash(r10!"\x01\x23\x45\x67\x89\xab\xcd\xef") == 0xea777a45U);
assert(hash(r10!"1032547698BADCFE") == 0x8f21c305U);
assert(hash(r10!"\x10\x32\x54\x76\x98\xba\xdc\xfe") == 0x5c9d0865U);
assert(hash(r500!"\x00") == 0xfa823dd5U);
assert(hash(r500!"\x07") == 0x21a27271U);
assert(hash(r500!"~") == 0x83c5c6d5U);
assert(hash(r500!"\x7f") == 0x813b0881U);
}
// FNV-1a 64 bit test vectors
static if (size_t.sizeof == 8) @nogc nothrow pure @safe unittest
{
assert(hash("") == 0xcbf29ce484222325UL);
assert(hash("a") == 0xaf63dc4c8601ec8cUL);
assert(hash("b") == 0xaf63df4c8601f1a5UL);
assert(hash("c") == 0xaf63de4c8601eff2UL);
assert(hash("d") == 0xaf63d94c8601e773UL);
assert(hash("e") == 0xaf63d84c8601e5c0UL);
assert(hash("f") == 0xaf63db4c8601ead9UL);
assert(hash("fo") == 0x08985907b541d342UL);
assert(hash("foo") == 0xdcb27518fed9d577UL);
assert(hash("foob") == 0xdd120e790c2512afUL);
assert(hash("fooba") == 0xcac165afa2fef40aUL);
assert(hash("foobar") == 0x85944171f73967e8UL);
assert(hash("\0") == 0xaf63bd4c8601b7dfUL);
assert(hash("a\0") == 0x089be207b544f1e4UL);
assert(hash("b\0") == 0x08a61407b54d9b5fUL);
assert(hash("c\0") == 0x08a2ae07b54ab836UL);
assert(hash("d\0") == 0x0891b007b53c4869UL);
assert(hash("e\0") == 0x088e4a07b5396540UL);
assert(hash("f\0") == 0x08987c07b5420ebbUL);
assert(hash("fo\0") == 0xdcb28a18fed9f926UL);
assert(hash("foo\0") == 0xdd1270790c25b935UL);
assert(hash("foob\0") == 0xcac146afa2febf5dUL);
assert(hash("fooba\0") == 0x8593d371f738acfeUL);
assert(hash("foobar\0") == 0x34531ca7168b8f38UL);
assert(hash("ch") == 0x08a25607b54a22aeUL);
assert(hash("cho") == 0xf5faf0190cf90df3UL);
assert(hash("chon") == 0xf27397910b3221c7UL);
assert(hash("chong") == 0x2c8c2b76062f22e0UL);
assert(hash("chongo") == 0xe150688c8217b8fdUL);
assert(hash("chongo ") == 0xf35a83c10e4f1f87UL);
assert(hash("chongo w") == 0xd1edd10b507344d0UL);
assert(hash("chongo wa") == 0x2a5ee739b3ddb8c3UL);
assert(hash("chongo was") == 0xdcfb970ca1c0d310UL);
assert(hash("chongo was ") == 0x4054da76daa6da90UL);
assert(hash("chongo was h") == 0xf70a2ff589861368UL);
assert(hash("chongo was he") == 0x4c628b38aed25f17UL);
assert(hash("chongo was her") == 0x9dd1f6510f78189fUL);
assert(hash("chongo was here") == 0xa3de85bd491270ceUL);
assert(hash("chongo was here!") == 0x858e2fa32a55e61dUL);
assert(hash("chongo was here!\n") == 0x46810940eff5f915UL);
assert(hash("ch\0") == 0xf5fadd190cf8edaaUL);
assert(hash("cho\0") == 0xf273ed910b32b3e9UL);
assert(hash("chon\0") == 0x2c8c5276062f6525UL);
assert(hash("chong\0") == 0xe150b98c821842a0UL);
assert(hash("chongo\0") == 0xf35aa3c10e4f55e7UL);
assert(hash("chongo \0") == 0xd1ed680b50729265UL);
assert(hash("chongo w\0") == 0x2a5f0639b3dded70UL);
assert(hash("chongo wa\0") == 0xdcfbaa0ca1c0f359UL);
assert(hash("chongo was\0") == 0x4054ba76daa6a430UL);
assert(hash("chongo was \0") == 0xf709c7f5898562b0UL);
assert(hash("chongo was h\0") == 0x4c62e638aed2f9b8UL);
assert(hash("chongo was he\0") == 0x9dd1a8510f779415UL);
assert(hash("chongo was her\0") == 0xa3de2abd4911d62dUL);
assert(hash("chongo was here\0") == 0x858e0ea32a55ae0aUL);
assert(hash("chongo was here!\0") == 0x46810f40eff60347UL);
assert(hash("chongo was here!\n\0") == 0xc33bce57bef63eafUL);
assert(hash("cu") == 0x08a24307b54a0265UL);
assert(hash("cur") == 0xf5b9fd190cc18d15UL);
assert(hash("curd") == 0x4c968290ace35703UL);
assert(hash("curds") == 0x07174bd5c64d9350UL);
assert(hash("curds ") == 0x5a294c3ff5d18750UL);
assert(hash("curds a") == 0x05b3c1aeb308b843UL);
assert(hash("curds an") == 0xb92a48da37d0f477UL);
assert(hash("curds and") == 0x73cdddccd80ebc49UL);
assert(hash("curds and ") == 0xd58c4c13210a266bUL);
assert(hash("curds and w") == 0xe78b6081243ec194UL);
assert(hash("curds and wh") == 0xb096f77096a39f34UL);
assert(hash("curds and whe") == 0xb425c54ff807b6a3UL);
assert(hash("curds and whey") == 0x23e520e2751bb46eUL);
assert(hash("curds and whey\n") == 0x1a0b44ccfe1385ecUL);
assert(hash("cu\0") == 0xf5ba4b190cc2119fUL);
assert(hash("cur\0") == 0x4c962690ace2baafUL);
assert(hash("curd\0") == 0x0716ded5c64cda19UL);
assert(hash("curds\0") == 0x5a292c3ff5d150f0UL);
assert(hash("curds \0") == 0x05b3e0aeb308ecf0UL);
assert(hash("curds a\0") == 0xb92a5eda37d119d9UL);
assert(hash("curds an\0") == 0x73ce41ccd80f6635UL);
assert(hash("curds and\0") == 0xd58c2c132109f00bUL);
assert(hash("curds and \0") == 0xe78baf81243f47d1UL);
assert(hash("curds and w\0") == 0xb0968f7096a2ee7cUL);
assert(hash("curds and wh\0") == 0xb425a84ff807855cUL);
assert(hash("curds and whe\0") == 0x23e4e9e2751b56f9UL);
assert(hash("curds and whey\0") == 0x1a0b4eccfe1396eaUL);
assert(hash("curds and whey\n\0") == 0x54abd453bb2c9004UL);
assert(hash("hi") == 0x08ba5f07b55ec3daUL);
assert(hash("hi\0") == 0x337354193006cb6eUL);
assert(hash("hello") == 0xa430d84680aabd0bUL);
assert(hash("hello\0") == 0xa9bc8acca21f39b1UL);
assert(hash("\xff\x00\x00\x01") == 0x6961196491cc682dUL);
assert(hash("\x01\x00\x00\xff") == 0xad2bb1774799dfe9UL);
assert(hash("\xff\x00\x00\x02") == 0x6961166491cc6314UL);
assert(hash("\x02\x00\x00\xff") == 0x8d1bb3904a3b1236UL);
assert(hash("\xff\x00\x00\x03") == 0x6961176491cc64c7UL);
assert(hash("\x03\x00\x00\xff") == 0xed205d87f40434c7UL);
assert(hash("\xff\x00\x00\x04") == 0x6961146491cc5faeUL);
assert(hash("\x04\x00\x00\xff") == 0xcd3baf5e44f8ad9cUL);
assert(hash("\x40\x51\x4e\x44") == 0xe3b36596127cd6d8UL);
assert(hash("\x44\x4e\x51\x40") == 0xf77f1072c8e8a646UL);
assert(hash("\x40\x51\x4e\x4a") == 0xe3b36396127cd372UL);
assert(hash("\x4a\x4e\x51\x40") == 0x6067dce9932ad458UL);
assert(hash("\x40\x51\x4e\x54") == 0xe3b37596127cf208UL);
assert(hash("\x54\x4e\x51\x40") == 0x4b7b10fa9fe83936UL);
assert(hash("127.0.0.1") == 0xaabafe7104d914beUL);
assert(hash("127.0.0.1\0") == 0xf4d3180b3cde3edaUL);
assert(hash("127.0.0.2") == 0xaabafd7104d9130bUL);
assert(hash("127.0.0.2\0") == 0xf4cfb20b3cdb5bb1UL);
assert(hash("127.0.0.3") == 0xaabafc7104d91158UL);
assert(hash("127.0.0.3\0") == 0xf4cc4c0b3cd87888UL);
assert(hash("64.81.78.68") == 0xe729bac5d2a8d3a7UL);
assert(hash("64.81.78.68\0") == 0x74bc0524f4dfa4c5UL);
assert(hash("64.81.78.74") == 0xe72630c5d2a5b352UL);
assert(hash("64.81.78.74\0") == 0x6b983224ef8fb456UL);
assert(hash("64.81.78.84") == 0xe73042c5d2ae266dUL);
assert(hash("64.81.78.84\0") == 0x8527e324fdeb4b37UL);
assert(hash("feedface") == 0x0a83c86fee952abcUL);
assert(hash("feedface\0") == 0x7318523267779d74UL);
assert(hash("feedfacedaffdeed") == 0x3e66d3d56b8caca1UL);
assert(hash("feedfacedaffdeed\0") == 0x956694a5c0095593UL);
assert(hash("feedfacedeadbeef") == 0xcac54572bb1a6fc8UL);
assert(hash("feedfacedeadbeef\0") == 0xa7a4c9f3edebf0d8UL);
assert(hash("line 1\nline 2\nline 3") == 0x7829851fac17b143UL);
assert(hash("chongo <Landon Curt Noll> /\\../\\") == 0x2c8f4c9af81bcf06UL);
assert(hash("chongo <Landon Curt Noll> /\\../\\\0") == 0xd34e31539740c732UL);
assert(hash("chongo (Landon Curt Noll) /\\../\\") == 0x3605a2ac253d2db1UL);
assert(hash("chongo (Landon Curt Noll) /\\../\\\0") == 0x08c11b8346f4a3c3UL);
assert(hash("http://antwrp.gsfc.nasa.gov/apod/astropix.html") == 0x6be396289ce8a6daUL);
assert(hash("http://en.wikipedia.org/wiki/Fowler_Noll_Vo_hash") == 0xd9b957fb7fe794c5UL);
assert(hash("http://epod.usra.edu/") == 0x05be33da04560a93UL);
assert(hash("http://exoplanet.eu/") == 0x0957f1577ba9747cUL);
assert(hash("http://hvo.wr.usgs.gov/cam3/") == 0xda2cc3acc24fba57UL);
assert(hash("http://hvo.wr.usgs.gov/cams/HMcam/") == 0x74136f185b29e7f0UL);
assert(hash("http://hvo.wr.usgs.gov/kilauea/update/deformation.html") == 0xb2f2b4590edb93b2UL);
assert(hash("http://hvo.wr.usgs.gov/kilauea/update/images.html") == 0xb3608fce8b86ae04UL);
assert(hash("http://hvo.wr.usgs.gov/kilauea/update/maps.html") == 0x4a3a865079359063UL);
assert(hash("http://hvo.wr.usgs.gov/volcanowatch/current_issue.html") == 0x5b3a7ef496880a50UL);
assert(hash("http://neo.jpl.nasa.gov/risk/") == 0x48fae3163854c23bUL);
assert(hash("http://norvig.com/21-days.html") == 0x07aaa640476e0b9aUL);
assert(hash("http://primes.utm.edu/curios/home.php") == 0x2f653656383a687dUL);
assert(hash("http://slashdot.org/") == 0xa1031f8e7599d79cUL);
assert(hash("http://tux.wr.usgs.gov/Maps/155.25-19.5.html") == 0xa31908178ff92477UL);
assert(hash("http://volcano.wr.usgs.gov/kilaueastatus.php") == 0x097edf3c14c3fb83UL);
assert(hash("http://www.avo.alaska.edu/activity/Redoubt.php") == 0xb51ca83feaa0971bUL);
assert(hash("http://www.dilbert.com/fast/") == 0xdd3c0d96d784f2e9UL);
assert(hash("http://www.fourmilab.ch/gravitation/orbits/") == 0x86cd26a9ea767d78UL);
assert(hash("http://www.fpoa.net/") == 0xe6b215ff54a30c18UL);
assert(hash("http://www.ioccc.org/index.html") == 0xec5b06a1c5531093UL);
assert(hash("http://www.isthe.com/cgi-bin/number.cgi") == 0x45665a929f9ec5e5UL);
assert(hash("http://www.isthe.com/chongo/bio.html") == 0x8c7609b4a9f10907UL);
assert(hash("http://www.isthe.com/chongo/index.html") == 0x89aac3a491f0d729UL);
assert(hash("http://www.isthe.com/chongo/src/calc/lucas-calc") == 0x32ce6b26e0f4a403UL);
assert(hash("http://www.isthe.com/chongo/tech/astro/venus2004.html") == 0x614ab44e02b53e01UL);
assert(hash("http://www.isthe.com/chongo/tech/astro/vita.html") == 0xfa6472eb6eef3290UL);
assert(hash("http://www.isthe.com/chongo/tech/comp/c/expert.html") == 0x9e5d75eb1948eb6aUL);
assert(hash("http://www.isthe.com/chongo/tech/comp/calc/index.html") == 0xb6d12ad4a8671852UL);
assert(hash("http://www.isthe.com/chongo/tech/comp/fnv/index.html") == 0x88826f56eba07af1UL);
assert(hash("http://www.isthe.com/chongo/tech/math/number/howhigh.html") == 0x44535bf2645bc0fdUL);
assert(hash("http://www.isthe.com/chongo/tech/math/number/number.html") == 0x169388ffc21e3728UL);
assert(hash("http://www.isthe.com/chongo/tech/math/prime/mersenne.html") == 0xf68aac9e396d8224UL);
assert(hash("http://www.isthe.com/chongo/tech/math/prime/mersenne.html#largest") == 0x8e87d7e7472b3883UL);
assert(hash("http://www.lavarnd.org/cgi-bin/corpspeak.cgi") == 0x295c26caa8b423deUL);
assert(hash("http://www.lavarnd.org/cgi-bin/haiku.cgi") == 0x322c814292e72176UL);
assert(hash("http://www.lavarnd.org/cgi-bin/rand-none.cgi") == 0x8a06550eb8af7268UL);
assert(hash("http://www.lavarnd.org/cgi-bin/randdist.cgi") == 0xef86d60e661bcf71UL);
assert(hash("http://www.lavarnd.org/index.html") == 0x9e5426c87f30ee54UL);
assert(hash("http://www.lavarnd.org/what/nist-test.html") == 0xf1ea8aa826fd047eUL);
assert(hash("http://www.macosxhints.com/") == 0x0babaf9a642cb769UL);
assert(hash("http://www.mellis.com/") == 0x4b3341d4068d012eUL);
assert(hash("http://www.nature.nps.gov/air/webcams/parks/havoso2alert/havoalert.cfm") == 0xd15605cbc30a335cUL);
assert(hash("http://www.nature.nps.gov/air/webcams/parks/havoso2alert/timelines_24.cfm") == 0x5b21060aed8412e5UL);
assert(hash("http://www.paulnoll.com/") == 0x45e2cda1ce6f4227UL);
assert(hash("http://www.pepysdiary.com/") == 0x50ae3745033ad7d4UL);
assert(hash("http://www.sciencenews.org/index/home/activity/view") == 0xaa4588ced46bf414UL);
assert(hash("http://www.skyandtelescope.com/") == 0xc1b0056c4a95467eUL);
assert(hash("http://www.sput.nl/~rob/sirius.html") == 0x56576a71de8b4089UL);
assert(hash("http://www.systemexperts.com/") == 0xbf20965fa6dc927eUL);
assert(hash("http://www.tq-international.com/phpBB3/index.php") == 0x569f8383c2040882UL);
assert(hash("http://www.travelquesttours.com/index.htm") == 0xe1e772fba08feca0UL);
assert(hash("http://www.wunderground.com/global/stations/89606.html") == 0x4ced94af97138ac4UL);
assert(hash(r10!"21701") == 0xc4112ffb337a82fbUL);
assert(hash(r10!"M21701") == 0xd64a4fd41de38b7dUL);
assert(hash(r10!"2^21701-1") == 0x4cfc32329edebcbbUL);
assert(hash(r10!"\x54\xc5") == 0x0803564445050395UL);
assert(hash(r10!"\xc5\x54") == 0xaa1574ecf4642ffdUL);
assert(hash(r10!"23209") == 0x694bc4e54cc315f9UL);
assert(hash(r10!"M23209") == 0xa3d7cb273b011721UL);
assert(hash(r10!"2^23209-1") == 0x577c2f8b6115bfa5UL);
assert(hash(r10!"\x5a\xa9") == 0xb7ec8c1a769fb4c1UL);
assert(hash(r10!"\xa9\x5a") == 0x5d5cfce63359ab19UL);
assert(hash(r10!"391581216093") == 0x33b96c3cd65b5f71UL);
assert(hash(r10!"391581*2^216093-1") == 0xd845097780602bb9UL);
assert(hash(r10!"\x05\xf9\x9d\x03\x4c\x81") == 0x84d47645d02da3d5UL);
assert(hash(r10!"FEDCBA9876543210") == 0x83544f33b58773a5UL);
assert(hash(r10!"\xfe\xdc\xba\x98\x76\x54\x32\x10") == 0x9175cbb2160836c5UL);
assert(hash(r10!"EFCDAB8967452301") == 0xc71b3bc175e72bc5UL);
assert(hash(r10!"\xef\xcd\xab\x89\x67\x45\x23\x01") == 0x636806ac222ec985UL);
assert(hash(r10!"0123456789ABCDEF") == 0xb6ef0e6950f52ed5UL);
assert(hash(r10!"\x01\x23\x45\x67\x89\xab\xcd\xef") == 0xead3d8a0f3dfdaa5UL);
assert(hash(r10!"1032547698BADCFE") == 0x922908fe9a861ba5UL);
assert(hash(r10!"\x10\x32\x54\x76\x98\xba\xdc\xfe") == 0x6d4821de275fd5c5UL);
assert(hash(r500!"\x00") == 0x1fe3fce62bd816b5UL);
assert(hash(r500!"\x07") == 0xc23e9fccd6f70591UL);
assert(hash(r500!"~") == 0xc1af12bdfe16b5b5UL);
assert(hash(r500!"\x7f") == 0x39e9f18f2f85e221UL);
}
private enum string r10(string x) = x ~ x ~ x ~ x ~ x ~ x ~ x ~ x ~ x ~ x;
private enum string r100(string x) = r10!x ~ r10!x ~ r10!x ~ r10!x ~ r10!x
~ r10!x ~ r10!x ~ r10!x ~ r10!x ~ r10!x;
private enum string r500(string x) = r100!x ~ r100!x ~ r100!x ~ r100!x ~ r100!x;
private struct HashRange
{
string fo = "fo";
@property ubyte front() const @nogc nothrow pure @safe
{
return this.fo[0];
}
void popFront() @nogc nothrow pure @safe
{
this.fo = this.fo[1 .. $];
}
@property bool empty() const @nogc nothrow pure @safe
{
return this.fo.length == 0;
}
}
private struct ToHashRange
{
bool empty_;
@property Hashable front() const @nogc nothrow pure @safe
{
return Hashable();
}
void popFront() @nogc nothrow pure @safe
{
this.empty_ = true;
}
@property bool empty() const @nogc nothrow pure @safe
{
return this.empty_;
}
}

View File

@ -0,0 +1,19 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.math.tests;
import tanya.math;
static if (ieeePrecision!float == IEEEPrecision.doubleExtended)
@nogc nothrow pure @safe unittest
{
assert(classify(1.68105e-10) == FloatingPointClass.normal);
assert(classify(1.68105e-4932L) == FloatingPointClass.subnormal);
// Emulate unnormals, because they aren't generated anymore since i386
FloatBits!real unnormal;
unnormal.exp = 0x123;
unnormal.mantissa = 0x1;
assert(classify(unnormal) == FloatingPointClass.subnormal);
}

View File

@ -0,0 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.math.tests.random;
import tanya.math.random;
import tanya.memory.allocator;
static if (is(PlatformEntropySource)) @nogc @system unittest
{
import tanya.memory.smartref : unique;
auto source = defaultAllocator.unique!PlatformEntropySource();
assert(source.threshold == 32);
assert(source.strong);
}

View File

@ -0,0 +1,177 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.memory.tests.lifetime;
import tanya.memory.allocator;
import tanya.memory.lifetime;
import tanya.test.stub;
@nogc nothrow pure @safe unittest
{
int[] p;
p = defaultAllocator.resize(p, 20);
assert(p.length == 20);
p = defaultAllocator.resize(p, 30);
assert(p.length == 30);
p = defaultAllocator.resize(p, 10);
assert(p.length == 10);
p = defaultAllocator.resize(p, 0);
assert(p is null);
}
@nogc nothrow pure @system unittest
{
static struct S
{
~this() @nogc nothrow pure @safe
{
}
}
auto p = cast(S[]) defaultAllocator.allocate(S.sizeof);
defaultAllocator.dispose(p);
}
// Works with interfaces.
@nogc nothrow pure @safe unittest
{
interface I
{
}
class C : I
{
}
auto c = defaultAllocator.make!C();
I i = c;
defaultAllocator.dispose(i);
defaultAllocator.dispose(i);
}
// Handles "Cannot access frame pointer" error.
@nogc nothrow pure @safe unittest
{
struct F
{
~this() @nogc nothrow pure @safe
{
}
}
static assert(is(typeof(emplace!F((void[]).init))));
}
// Can emplace structs without a constructor
@nogc nothrow pure @safe unittest
{
static assert(is(typeof(emplace!WithDtor(null, WithDtor()))));
static assert(is(typeof(emplace!WithDtor(null))));
}
// Doesn't call a destructor on uninitialized elements
@nogc nothrow pure @system unittest
{
static struct SWithDtor
{
private bool canBeInvoked = false;
~this() @nogc nothrow pure @safe
{
assert(this.canBeInvoked);
}
}
void[SWithDtor.sizeof] memory = void;
auto actual = emplace!SWithDtor(memory[], SWithDtor(true));
assert(actual.canBeInvoked);
}
// Initializes structs if no arguments are given
@nogc nothrow pure @safe unittest
{
static struct SEntry
{
byte content;
}
ubyte[1] mem = [3];
assert(emplace!SEntry(cast(void[]) mem[0 .. 1]).content == 0);
}
// Postblit is called when emplacing a struct
@nogc nothrow pure @system unittest
{
static struct S
{
bool called = false;
this(this) @nogc nothrow pure @safe
{
this.called = true;
}
}
S target;
S* sp = &target;
emplace!S(sp[0 .. 1], S());
assert(target.called);
}
// Is pure.
@nogc nothrow pure @system unittest
{
struct S
{
this(this)
{
}
}
S source, target = void;
static assert(is(typeof({ moveEmplace(source, target); })));
}
// Moves nested.
@nogc nothrow pure @system unittest
{
struct Nested
{
void method() @nogc nothrow pure @safe
{
}
}
Nested source, target = void;
moveEmplace(source, target);
assert(source == target);
}
// Emplaces static arrays.
@nogc nothrow pure @system unittest
{
static struct S
{
size_t member;
this(size_t i) @nogc nothrow pure @safe
{
this.member = i;
}
~this() @nogc nothrow pure @safe
{
}
}
S[2] source = [ S(5), S(5) ], target = void;
moveEmplace(source, target);
assert(source[0].member == 0);
assert(target[0].member == 5);
assert(source[1].member == 0);
assert(target[1].member == 5);
}
// Moves if source is target.
@nogc nothrow pure @safe unittest
{
int x = 5;
move(x, x);
assert(x == 5);
}

View File

@ -0,0 +1,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.memory.tests.mallocator;
import tanya.memory.mallocator;
// Fails with false
@nogc nothrow pure @system unittest
{
void[] p = Mallocator.instance.allocate(20);
void[] oldP = p;
assert(!Mallocator.instance.reallocate(p, size_t.max - 16));
assert(oldP is p);
Mallocator.instance.deallocate(p);
}
@nogc nothrow pure unittest
{
assert(Mallocator.instance.alignment == (void*).alignof);
}

View File

@ -0,0 +1,133 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.memory.tests.mmappool;
import tanya.memory.mmappool;
@nogc nothrow pure @system unittest
{
auto p = MmapPool.instance.allocate(20);
assert(p);
MmapPool.instance.deallocate(p);
p = MmapPool.instance.allocate(0);
assert(p.length == 0);
}
@nogc nothrow pure @system unittest
{
auto p = MmapPool.instance.allocate(20);
assert(MmapPool.instance.deallocate(p));
}
@nogc nothrow pure @system unittest
{
void[] p;
assert(!MmapPool.instance.reallocateInPlace(p, 5));
assert(p is null);
p = MmapPool.instance.allocate(1);
auto orig = p.ptr;
assert(MmapPool.instance.reallocateInPlace(p, 2));
assert(p.length == 2);
assert(p.ptr == orig);
assert(MmapPool.instance.reallocateInPlace(p, 4));
assert(p.length == 4);
assert(p.ptr == orig);
assert(MmapPool.instance.reallocateInPlace(p, 2));
assert(p.length == 2);
assert(p.ptr == orig);
MmapPool.instance.deallocate(p);
}
@nogc nothrow pure @system unittest
{
void[] p;
MmapPool.instance.reallocate(p, 10 * int.sizeof);
(cast(int[]) p)[7] = 123;
assert(p.length == 40);
MmapPool.instance.reallocate(p, 8 * int.sizeof);
assert(p.length == 32);
assert((cast(int[]) p)[7] == 123);
MmapPool.instance.reallocate(p, 20 * int.sizeof);
(cast(int[]) p)[15] = 8;
assert(p.length == 80);
assert((cast(int[]) p)[15] == 8);
assert((cast(int[]) p)[7] == 123);
MmapPool.instance.reallocate(p, 8 * int.sizeof);
assert(p.length == 32);
assert((cast(int[]) p)[7] == 123);
MmapPool.instance.deallocate(p);
}
// A lot of allocations/deallocations, but it is the minimum caused a
// segmentation fault because MmapPool reallocateInPlace moves a block wrong.
@nogc nothrow pure @system unittest
{
auto a = MmapPool.instance.allocate(16);
auto d = MmapPool.instance.allocate(16);
auto b = MmapPool.instance.allocate(16);
auto e = MmapPool.instance.allocate(16);
auto c = MmapPool.instance.allocate(16);
auto f = MmapPool.instance.allocate(16);
MmapPool.instance.deallocate(a);
MmapPool.instance.deallocate(b);
MmapPool.instance.deallocate(c);
a = MmapPool.instance.allocate(50);
MmapPool.instance.reallocateInPlace(a, 64);
MmapPool.instance.deallocate(a);
a = MmapPool.instance.allocate(1);
auto tmp1 = MmapPool.instance.allocate(1);
auto h1 = MmapPool.instance.allocate(1);
auto tmp2 = cast(ubyte[]) MmapPool.instance.allocate(1);
auto h2 = MmapPool.instance.allocate(2);
tmp1 = MmapPool.instance.allocate(1);
MmapPool.instance.deallocate(h2);
MmapPool.instance.deallocate(h1);
h2 = MmapPool.instance.allocate(2);
h1 = MmapPool.instance.allocate(1);
MmapPool.instance.deallocate(h2);
auto rep = cast(void[]) tmp2;
MmapPool.instance.reallocate(rep, tmp1.length);
tmp2 = cast(ubyte[]) rep;
MmapPool.instance.reallocate(tmp1, 9);
rep = cast(void[]) tmp2;
MmapPool.instance.reallocate(rep, tmp1.length);
tmp2 = cast(ubyte[]) rep;
MmapPool.instance.reallocate(tmp1, 17);
tmp2[$ - 1] = 0;
MmapPool.instance.deallocate(tmp1);
b = MmapPool.instance.allocate(16);
MmapPool.instance.deallocate(h1);
MmapPool.instance.deallocate(a);
MmapPool.instance.deallocate(b);
MmapPool.instance.deallocate(d);
MmapPool.instance.deallocate(e);
MmapPool.instance.deallocate(f);
}

View File

@ -0,0 +1,67 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.memory.tests.op;
import tanya.memory.op;
@nogc nothrow pure @system unittest
{
ubyte[2] buffer = 1;
fill!0(buffer[1 .. $]);
assert(buffer[0] == 1 && buffer[1] == 0);
}
@nogc nothrow pure @safe unittest
{
assert(equal(null, null));
}
@nogc nothrow pure @safe unittest
{
ubyte[0] source, target;
source.copy(target);
}
@nogc nothrow pure @safe unittest
{
ubyte[1] source = [1];
ubyte[1] target;
source.copy(target);
assert(target[0] == 1);
}
@nogc nothrow pure @safe unittest
{
ubyte[8] source = [1, 2, 3, 4, 5, 6, 7, 8];
ubyte[8] target;
source.copy(target);
assert(equal(source, target));
}
@nogc nothrow pure @safe unittest
{
ubyte[9] r1 = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' ];
ubyte[9] r2;
copyBackward(r1, r2);
assert(equal(r1, r2));
}
// Compares unanligned memory
@nogc nothrow pure @safe unittest
{
ubyte[16] r1 = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
];
ubyte[16] r2 = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
];
assert(equal(r1, r2));
assert(equal(r1[1 .. $], r2[1 .. $]));
assert(equal(r1[0 .. $ - 1], r2[0 .. $ - 1]));
assert(equal(r1[0 .. 8], r2[0 .. 8]));
}

View File

@ -0,0 +1,253 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.memory.tests.smartref;
import tanya.memory.allocator;
import tanya.memory.smartref;
import tanya.meta.trait;
import tanya.test.stub;
@nogc @system unittest
{
auto rc = defaultAllocator.refCounted!int(5);
rc = defaultAllocator.make!int(7);
assert(*rc == 7);
}
@nogc @system unittest
{
RefCounted!int rc;
assert(!rc.isInitialized);
rc = null;
assert(!rc.isInitialized);
}
@nogc @system unittest
{
auto rc = defaultAllocator.refCounted!int(5);
void func(RefCounted!int param) @nogc
{
assert(param.count == 2);
param = defaultAllocator.make!int(7);
assert(param.count == 1);
assert(*param == 7);
}
func(rc);
assert(rc.count == 1);
assert(*rc == 5);
}
@nogc @system unittest
{
RefCounted!int rc;
void func(RefCounted!int param) @nogc
{
assert(param.count == 0);
param = defaultAllocator.make!int(7);
assert(param.count == 1);
assert(*param == 7);
}
func(rc);
assert(rc.count == 0);
}
@nogc @system unittest
{
RefCounted!int rc1, rc2;
static assert(is(typeof(rc1 = rc2)));
}
@nogc @system unittest
{
auto rc = RefCounted!int(defaultAllocator);
assert(!rc.isInitialized);
assert(rc.allocator is defaultAllocator);
}
@nogc @system unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(rc.count == 1);
void func(RefCounted!int rc) @nogc
{
assert(rc.count == 2);
rc = null;
assert(!rc.isInitialized);
assert(rc.count == 0);
}
assert(rc.count == 1);
func(rc);
assert(rc.count == 1);
rc = null;
assert(!rc.isInitialized);
assert(rc.count == 0);
}
@nogc @system unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(*rc == 5);
void func(RefCounted!int rc) @nogc
{
assert(rc.count == 2);
rc = defaultAllocator.refCounted!int(4);
assert(*rc == 4);
assert(rc.count == 1);
}
func(rc);
assert(*rc == 5);
}
@nogc @system unittest
{
auto rc = defaultAllocator.refCounted!(int[])(5);
assert(rc.length == 5);
}
@nogc @system unittest
{
auto p1 = defaultAllocator.make!int(5);
auto p2 = p1;
auto rc = RefCounted!int(p1, defaultAllocator);
assert(rc.get() is p2);
}
@nogc @system unittest
{
size_t destroyed;
{
auto rc = defaultAllocator.refCounted!WithDtor(destroyed);
}
assert(destroyed == 1);
}
@nogc nothrow pure @system unittest
{
auto s = defaultAllocator.unique!int(5);
assert(*s == 5);
s = null;
assert(s is null);
}
@nogc nothrow pure @system unittest
{
auto s = defaultAllocator.unique!int(5);
assert(*s == 5);
s = defaultAllocator.unique!int(4);
assert(*s == 4);
}
@nogc nothrow pure @system unittest
{
auto p1 = defaultAllocator.make!int(5);
auto p2 = p1;
auto rc = Unique!int(p1, defaultAllocator);
assert(rc.get() is p2);
}
@nogc nothrow pure @system unittest
{
auto rc = Unique!int(defaultAllocator);
assert(rc.allocator is defaultAllocator);
}
@nogc @system unittest
{
uint destroyed;
auto a = defaultAllocator.make!A(destroyed);
assert(destroyed == 0);
{
auto rc = RefCounted!A(a, defaultAllocator);
assert(rc.count == 1);
void func(RefCounted!A rc) @nogc @system
{
assert(rc.count == 2);
}
func(rc);
assert(rc.count == 1);
}
assert(destroyed == 1);
RefCounted!int rc;
assert(rc.count == 0);
rc = defaultAllocator.make!int(8);
assert(rc.count == 1);
}
@nogc nothrow pure @safe unittest
{
static assert(is(ReturnType!(RefCounted!int.get) == inout int*));
static assert(is(ReturnType!(RefCounted!A.get) == inout A));
static assert(is(ReturnType!(RefCounted!B.get) == inout B*));
}
@nogc nothrow pure @safe unittest
{
static assert(is(RefCounted!B));
static assert(is(RefCounted!A));
}
@nogc @system unittest
{
struct E
{
}
auto b = defaultAllocator.refCounted!B(15);
static assert(is(typeof(b.prop) == int));
static assert(!is(typeof(defaultAllocator.refCounted!B())));
static assert(is(typeof(defaultAllocator.refCounted!E())));
static assert(!is(typeof(defaultAllocator.refCounted!E(5))));
{
auto rc = defaultAllocator.refCounted!B(3);
assert(rc.get().prop == 3);
}
{
auto rc = defaultAllocator.refCounted!E();
assert(rc.count);
}
}
@nogc nothrow pure @safe unittest
{
static assert(is(typeof(defaultAllocator.unique!B(5))));
static assert(is(typeof(defaultAllocator.unique!(int[])(5))));
}
private class A
{
uint *destroyed;
this(ref uint destroyed) @nogc
{
this.destroyed = &destroyed;
}
~this() @nogc
{
++(*destroyed);
}
}
private struct B
{
int prop;
@disable this();
this(int param1) @nogc
{
prop = param1;
}
}

View File

@ -0,0 +1,30 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.meta.tests.metafunction;
import tanya.meta.metafunction;
@nogc nothrow pure @safe unittest
{
enum cmp(int x, int y) = x - y;
static assert(isSorted!(cmp));
static assert(isSorted!(cmp, 1));
static assert(isSorted!(cmp, 1, 2, 2));
static assert(isSorted!(cmp, 1, 2, 2, 4));
static assert(isSorted!(cmp, 1, 2, 2, 4, 8));
static assert(!isSorted!(cmp, 32, 2, 2, 4, 8));
static assert(isSorted!(cmp, 32, 32));
}
@nogc nothrow pure @safe unittest
{
enum cmp(int x, int y) = x < y;
static assert(isSorted!(cmp));
static assert(isSorted!(cmp, 1));
static assert(isSorted!(cmp, 1, 2, 2));
static assert(isSorted!(cmp, 1, 2, 2, 4));
static assert(isSorted!(cmp, 1, 2, 2, 4, 8));
static assert(!isSorted!(cmp, 32, 2, 2, 4, 8));
static assert(isSorted!(cmp, 32, 32));
}

View File

@ -0,0 +1,172 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.meta.tests.trait;
import tanya.meta.metafunction;
import tanya.meta.trait;
// typeof(null) is not a pointer.
@nogc nothrow pure @safe unittest
{
static assert(!isPointer!(typeof(null)));
static assert(!isPointer!(const shared typeof(null)));
enum typeOfNull : typeof(null)
{
null_ = null,
}
static assert(!isPointer!typeOfNull);
}
@nogc nothrow pure @safe unittest
{
static struct S
{
@property int opCall()
{
return 0;
}
}
S s;
static assert(isCallable!S);
static assert(isCallable!s);
}
@nogc nothrow pure @safe unittest
{
static assert(is(FunctionTypeOf!(void delegate()) == function));
static void staticFunc()
{
}
auto functionPointer = &staticFunc;
static assert(is(FunctionTypeOf!staticFunc == function));
static assert(is(FunctionTypeOf!functionPointer == function));
void func()
{
}
auto dg = &func;
static assert(is(FunctionTypeOf!func == function));
static assert(is(FunctionTypeOf!dg == function));
interface I
{
@property int prop();
}
static assert(is(FunctionTypeOf!(I.prop) == function));
static struct S
{
void opCall()
{
}
}
class C
{
static void opCall()
{
}
}
S s;
static assert(is(FunctionTypeOf!s == function));
static assert(is(FunctionTypeOf!C == function));
static assert(is(FunctionTypeOf!S == function));
}
@nogc nothrow pure @safe unittest
{
static struct S2
{
@property int opCall()
{
return 0;
}
}
S2 s2;
static assert(is(FunctionTypeOf!S2 == function));
static assert(is(FunctionTypeOf!s2 == function));
}
@nogc nothrow pure @safe unittest
{
static assert(!hasElaborateAssign!int);
static struct S1
{
void opAssign(S1)
{
}
}
static struct S2
{
void opAssign(int)
{
}
}
static struct S3
{
S1 s;
alias s this;
}
static assert(hasElaborateAssign!S1);
static assert(!hasElaborateAssign!(const S1));
static assert(hasElaborateAssign!(S1[1]));
static assert(!hasElaborateAssign!(S1[0]));
static assert(!hasElaborateAssign!S2);
static assert(hasElaborateAssign!S3);
static struct S4
{
void opAssign(S4)
{
}
@disable this(this);
}
static assert(hasElaborateAssign!S4);
}
// Produces a tuple for an enum with only one member
@nogc nothrow pure @safe unittest
{
enum E : int
{
one = 0,
}
static assert(EnumMembers!E == AliasSeq!0);
}
@nogc nothrow pure @safe unittest
{
class RefCountedStore(T)
{
}
static assert(!isInnerClass!(RefCountedStore!int));
}
@nogc nothrow pure @safe unittest
{
static struct DisabledOpEquals
{
@disable bool opEquals(typeof(this)) @nogc nothrow pure @safe;
int opCmp(typeof(this)) @nogc nothrow pure @safe
{
return 0;
}
}
static assert(!isEqualityComparable!DisabledOpEquals);
static assert(isOrderingComparable!DisabledOpEquals);
static struct OpEquals
{
bool opEquals(typeof(this)) @nogc nothrow pure @safe
{
return true;
}
}
static assert(isEqualityComparable!OpEquals);
static assert(!isOrderingComparable!OpEquals);
}

View File

@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.net.tests.iface;
import std.algorithm.comparison;
import std.utf;
import tanya.net.iface;
@nogc nothrow @safe unittest
{
version (linux)
{
assert(equal(indexToName(1)[], "lo".byChar));
}
else version (Windows)
{
assert(equal(indexToName(1)[], "loopback_0"));
}
else
{
assert(equal(indexToName(1)[], "lo0"));
}
assert(indexToName(uint.max).empty);
}

View File

@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.net.tests.inet;
import tanya.net.inet;
import tanya.range;
// Static tests
@nogc nothrow pure @safe unittest
{
static assert(isBidirectionalRange!(NetworkOrder!4));
static assert(isBidirectionalRange!(NetworkOrder!8));
static assert(!is(NetworkOrder!9));
static assert(!is(NetworkOrder!1));
}

154
tests/tanya/net/tests/ip.d Normal file
View File

@ -0,0 +1,154 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.net.tests.ip;
import tanya.net.ip;
import tanya.range;
// Rejects malformed addresses
@nogc nothrow pure @safe unittest
{
assert(address4("256.0.0.1").isNull);
assert(address4(".0.0.1").isNull);
assert(address4("0..0.1").isNull);
assert(address4("0.0.0.").isNull);
assert(address4("0.0.").isNull);
assert(address4("").isNull);
}
@nogc nothrow pure @safe unittest
{
assert(address4(cast(ubyte[]) []).isNull);
}
// Assignment and comparison works
@nogc nothrow pure @safe unittest
{
auto address1 = Address4.loopback();
auto address2 = Address4.any();
address1 = address2;
assert(address1 == address2);
}
@nogc nothrow @safe unittest
{
char[18] actual;
address6("ff00:2:3:4:5:6:7:8").get.toString(arrayInserter(actual));
assert(actual[] == "ff00:2:3:4:5:6:7:8");
}
// Skips zero group in the middle
@nogc nothrow @safe unittest
{
char[12] actual;
address6("1::4:5:6:7:8").get.toString(arrayInserter(actual));
assert(actual[] == "1::4:5:6:7:8");
}
// Doesn't replace lonely zeroes
@nogc nothrow @safe unittest
{
char[15] actual;
address6("0:1:0:2:3:0:4:0").get.toString(arrayInserter(actual));
assert(actual[] == "0:1:0:2:3:0:4:0");
}
// Skips zero group at the beginning
@nogc nothrow @safe unittest
{
char[13] actual;
address6("::3:4:5:6:7:8").get.toString(arrayInserter(actual));
assert(actual[] == "::3:4:5:6:7:8");
}
// Skips zero group at the end
@nogc nothrow @safe unittest
{
char[13] actual;
address6("1:2:3:4:5:6::").get.toString(arrayInserter(actual));
assert(actual[] == "1:2:3:4:5:6::");
}
@nogc nothrow @safe unittest
{
ubyte[16] expected = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8];
auto actual = address6("1:2:3:4:5:6:7:8");
assert(actual.get.toBytes() == expected);
}
@nogc nothrow @safe unittest
{
ubyte[16] expected;
auto actual = address6("::");
assert(actual.get.toBytes() == expected);
}
@nogc nothrow @safe unittest
{
ubyte[16] expected = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
auto actual = address6("::1");
assert(actual.get.toBytes() == expected);
}
@nogc nothrow @safe unittest
{
ubyte[16] expected = [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
auto actual = address6("1::");
assert(actual.get.toBytes() == expected);
}
// Rejects malformed addresses
@nogc nothrow @safe unittest
{
assert(address6("").isNull);
assert(address6(":").isNull);
assert(address6(":a").isNull);
assert(address6("a:").isNull);
assert(address6("1:2:3:4::6:").isNull);
assert(address6("fe80:2:3:4::6:7:8%").isNull);
}
// Parses embedded IPv4 address
@nogc nothrow @safe unittest
{
ubyte[16] expected = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4];
auto actual = address6("0:0:0:0:0:0:1.2.3.4");
assert(actual.get.toBytes() == expected);
}
@nogc nothrow @safe unittest
{
ubyte[16] expected = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4];
auto actual = address6("::1.2.3.4");
assert(actual.get.toBytes() == expected);
}
@nogc nothrow @safe unittest
{
ubyte[16] expected = [0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 1, 2, 3, 4];
auto actual = address6("::5:6:1.2.3.4");
assert(actual.get.toBytes() == expected);
}
@nogc nothrow @safe unittest
{
assert(address6("0:0:0:0:0:0:1.2.3.").isNull);
assert(address6("0:0:0:0:0:0:1.2:3.4").isNull);
assert(address6("0:0:0:0:0:0:1.2.3.4.").isNull);
assert(address6("fe80:0:0:0:0:0:1.2.3.4%1").get.scopeID == 1);
}
// Can assign another address
@nogc nothrow pure @safe unittest
{
Address actual = Address4.loopback;
Address expected = Address6.loopback;
actual = expected;
assert(actual == expected);
}

136
tests/tanya/net/tests/uri.d Normal file
View File

@ -0,0 +1,136 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
module tanya.net.tests.uri;
import tanya.net.uri;
import tanya.test.assertion;
@nogc pure @system unittest
{
const u = URL("127.0.0.1");
assert(u.path == "127.0.0.1");
}
@nogc pure @system unittest
{
const u = URL("http://127.0.0.1");
assert(u.scheme == "http");
assert(u.host == "127.0.0.1");
}
@nogc pure @system unittest
{
const u = URL("http://127.0.0.1:9000");
assert(u.scheme == "http");
assert(u.host == "127.0.0.1");
assert(u.port == 9000);
}
@nogc pure @system unittest
{
const u = URL("127.0.0.1:80");
assert(u.host == "127.0.0.1");
assert(u.port == 80);
assert(u.path is null);
}
@nogc pure @system unittest
{
const u = URL("//example.net");
assert(u.host == "example.net");
assert(u.scheme is null);
}
@nogc pure @system unittest
{
const u = URL("//example.net?q=before:after");
assert(u.host == "example.net");
assert(u.query == "q=before:after");
}
@nogc pure @system unittest
{
const u = URL("localhost:8080");
assert(u.host == "localhost");
assert(u.port == 8080);
assert(u.path is null);
}
@nogc pure @system unittest
{
const u = URL("ftp:");
assert(u.scheme == "ftp");
}
@nogc pure @system unittest
{
const u = URL("file:///C:\\Users");
assert(u.scheme == "file");
assert(u.path == "C:\\Users");
}
@nogc pure @system unittest
{
const u = URL("localhost:66000");
assert(u.scheme == "localhost");
assert(u.path == "66000");
}
@nogc pure @system unittest
{
const u = URL("file:///home/");
assert(u.scheme == "file");
assert(u.path == "/home/");
}
@nogc pure @system unittest
{
const u = URL("file:///home/?q=asdf");
assert(u.scheme == "file");
assert(u.path == "/home/");
assert(u.query == "q=asdf");
}
@nogc pure @system unittest
{
const u = URL("http://secret@example.org");
assert(u.scheme == "http");
assert(u.host == "example.org");
assert(u.user == "secret");
}
@nogc pure @system unittest
{
const u = URL("h_tp://:80");
assert(u.path == "h_tp://:80");
assert(u.port == 0);
}
@nogc pure @system unittest
{
const u = URL("zlib:/home/user/file.gz");
assert(u.scheme == "zlib");
assert(u.path == "/home/user/file.gz");
}
@nogc pure @system unittest
{
const u = URL("h_tp:asdf");
assert(u.path == "h_tp:asdf");
}
@nogc pure @system unittest
{
assertThrown!URIException(() => URL("http://:80"));
assertThrown!URIException(() => URL(":80"));
assertThrown!URIException(() => URL("http://u1:p1@u2:p2@example.org"));
assertThrown!URIException(() => URL("http://blah.com:port"));
assertThrown!URIException(() => URL("http://blah.com:66000"));
}
@nogc pure @system unittest
{
const u = URL("ftp://");
assert(u.scheme == "ftp");
}

Some files were not shown because too many files have changed in this diff Show More