250 Commits

Author SHA1 Message Date
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
130 changed files with 17881 additions and 9750 deletions

7
.gitignore vendored
View File

@ -1,13 +1,16 @@
# Binary
*.[oa]
*.exe
*.lib
# D
.dub
dub.selections.json
__test__*__
__test__*__.core
/tanya-test-*
/dub_platform_probe-*
tanya-*test-*
/dub_platform_probe[_-]*
/docs/
/docs.json

View File

@ -1,33 +1,68 @@
sudo: false
os:
- linux
- osx
- linux
- osx
language: d
d:
- dmd-2.078.0
- dmd-2.077.1
- dmd-2.076.1
- dmd-2.091.1
env:
global:
- LATEST=2.091.1
env:
matrix:
- ARCH=x86_64
- ARCH=x86
- ARCH=x86_64
- ARCH=x86
matrix:
include:
- name: D-Scanner
d: dmd-$LATEST
env: DSCANNER=0.7.0
os: linux
- name: DDoc
d: dmd-$LATEST
env: DDOC=true
os: linux
addons:
apt:
packages:
- gcc-multilib
- gcc-multilib
before_script:
- if [ "$PS1" = '(dmd-2.078.0)' ]; then
export UNITTEST="unittest-cov";
fi
- if [ "`$DC --version | head -n 1 | grep v$LATEST`" ] &&
[ -z "$DSCANNER$DDOC" ]; then
export UNITTEST="unittest-cov";
fi
script:
- dub test -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC
- set -e;
if [ -n "$DDOC" ]; then
dub build :meta -b ddox --compiler=$DC;
dub build :sys -b ddox --compiler=$DC;
dub build :os -b ddox --compiler=$DC;
dub build :encoding -b ddox --compiler=$DC;
dub build :middle -b ddox --compiler=$DC;
dub build :test -b ddox --compiler=$DC;
dub build -b ddox --compiler=$DC;
elif [ -z "$DSCANNER" ]; then
dub test :meta -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC;
dub test :sys -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC;
dub test :os -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC;
dub test :encoding -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC;
dub test :middle -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC;
dub test :test -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC;
dub test -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC;
else
dub fetch dscanner --version=$DSCANNER;
FILES=$(find */tanya -type f);
dub run dscanner -- --styleCheck $FILES;
fi
after_success:
- test "$UNITTEST" = "unittest-cov" && bash <(curl -s https://codecov.io/bash)
- test "$UNITTEST" && bash <(curl -s https://codecov.io/bash) || true

View File

@ -1,6 +1,6 @@
# Tanya
[![Build status](https://travis-ci.org/caraus-ecms/tanya.svg?branch=master)](https://travis-ci.org/caraus-ecms/tanya)
[![Build Status](https://travis-ci.com/caraus-ecms/tanya.svg?branch=master)](https://travis-ci.com/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)
[![Dub version](https://img.shields.io/dub/v/tanya.svg)](https://code.dlang.org/packages/tanya)
@ -25,13 +25,16 @@ Tanya consists of the following packages and (top-level) modules:
* `algorithm`: Collection of generic algorithms.
* `async`: Event loop (epoll, kqueue and IOCP).
* `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.
* `encoding`: This package provides tools to work with text encodings.
* `exception`: Common exceptions and errors.
* `format`: Formatting and conversion functions.
* `functional`: Functions that manipulate other functions and their argument
lists.
* `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
@ -49,7 +52,10 @@ After finishing the new socket implementation will land in the `net` package and
ones.
## Basic usage
## NogcD
To achieve programming without the Garbage Collection tanya uses a subset of D:
NogcD.
### Allocators
@ -113,7 +119,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
@ -143,37 +149,47 @@ 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 | GCC |
|:-------:|:--------------:|
| 2.078.0 | *gdc-5* branch |
| 2.077.1 | |
| 2.076.1 | |
### 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) |
### Release management
Deprecated features are removed after one release that includes these deprecations.
| DMD | GCC |
|:-------:|:---------:|
| 2.091.1 | gdc trunk |
## 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

View File

@ -4,22 +4,10 @@ os: Visual Studio 2015
environment:
matrix:
- DC: dmd
DVersion: 2.078.0
DVersion: 2.091.1
arch: x64
- DC: dmd
DVersion: 2.078.0
arch: x86
- DC: dmd
DVersion: 2.077.1
arch: x64
- DC: dmd
DVersion: 2.077.1
arch: x86
- DC: dmd
DVersion: 2.076.1
arch: x64
- DC: dmd
DVersion: 2.076.1
DVersion: 2.091.1
arch: x86
skip_tags: true
@ -40,7 +28,7 @@ install:
before_build:
- ps: if($env:arch -eq "x86"){
$env:compilersetupargs = "x86";
$env:Darch = "x86";
$env:Darch = "x86_mscoff";
}
elseif($env:arch -eq "x64"){
$env:compilersetupargs = "amd64";
@ -57,4 +45,10 @@ test_script:
- echo %PATH%
- 'dub --version'
- '%DC% --version'
- dub test :meta -b unittest --arch=%Darch% --compiler=%DC%
- dub test :sys -b unittest --arch=%Darch% --compiler=%DC%
- dub test :os -b unittest --arch=%Darch% --compiler=%DC%
- dub test :encoding -b unittest --arch=%Darch% --compiler=%DC%
- dub test :middle -b unittest --arch=%Darch% --compiler=%DC%
- dub test :test -b unittest --arch=%Darch% --compiler=%DC%
- dub test -b unittest --arch=%Darch% --compiler=%DC%

View File

@ -6,9 +6,9 @@ rule archive
build abs.o: gas x64/linux/math/abs.S
build log.o: gas x64/linux/math/log.S
build cmp.o: gas x64/linux/memory/cmp.S
build equal.o: gas x64/linux/memory/equal.S
build fill.o: gas x64/linux/memory/fill.S
build copy.o: gas x64/linux/memory/copy.S
build syscall.o: gas x64/linux/syscall.S
build tanya.a: archive syscall.o copy.o fill.o cmp.o log.o abs.o
build tanya.a: archive syscall.o copy.o fill.o equal.o log.o abs.o

View File

@ -2,10 +2,10 @@
// fabsf.
.globl _D5tanya4math8nbtheory10__T3absTfZ3absFNaNbNiNffZf
.type _D5tanya4math8nbtheory10__T3absTfZ3absFNaNbNiNffZf, @function
.globl _D5tanya4math8nbtheory4fabsFNaNbNiNffZf
.type _D5tanya4math8nbtheory4fabsFNaNbNiNffZf, @function
_D5tanya4math8nbtheory10__T3absTfZ3absFNaNbNiNffZf:
_D5tanya4math8nbtheory4fabsFNaNbNiNffZf:
mov $0x7fffffff, %eax
movq %rax, %xmm1
andpd %xmm1, %xmm0
@ -13,10 +13,10 @@ _D5tanya4math8nbtheory10__T3absTfZ3absFNaNbNiNffZf:
// fabs.
.globl _D5tanya4math8nbtheory10__T3absTdZ3absFNaNbNiNfdZd
.type _D5tanya4math8nbtheory10__T3absTdZ3absFNaNbNiNfdZd, @function
.globl _D5tanya4math8nbtheory4fabsFNaNbNiNfdZd
.type _D5tanya4math8nbtheory4fabsFNaNbNiNfdZd, @function
_D5tanya4math8nbtheory10__T3absTdZ3absFNaNbNiNfdZd:
_D5tanya4math8nbtheory4fabsFNaNbNiNfdZd:
mov $0x7fffffffffffffff, %rax
movq %rax, %xmm1
andpd %xmm1, %xmm0
@ -24,12 +24,12 @@ _D5tanya4math8nbtheory10__T3absTdZ3absFNaNbNiNfdZd:
// fabsl.
.globl _D5tanya4math8nbtheory10__T3absTeZ3absFNaNbNiNfeZe
.type _D5tanya4math8nbtheory10__T3absTeZ3absFNaNbNiNfeZe, @function
.globl _D5tanya4math8nbtheory4fabsFNaNbNiNfeZe
.type _D5tanya4math8nbtheory4fabsFNaNbNiNfeZe, @function
// Load the parameter from the stack onto FP stack, execute 'fabs' instruction
// The result is returned in ST0.
_D5tanya4math8nbtheory10__T3absTeZ3absFNaNbNiNfeZe:
_D5tanya4math8nbtheory4fabsFNaNbNiNfeZe:
fldt 0x8(%rsp)
fabs
ret

View File

@ -1,22 +1,29 @@
.text
// logl.
.globl _D5tanya4math8nbtheory2lnFNaNbNiNfeZe
.type _D5tanya4math8nbtheory2lnFNaNbNiNfeZe, @function
// logf.
.globl _D5tanya4math8nbtheory4logfFNaNbNiNffZf
.type _D5tanya4math8nbtheory4logfFNaNbNiNffZf, @function
_D5tanya4math8nbtheory4logfFNaNbNiNffZf:
movss %xmm0, -4(%rsp) // Put the argument onto the stack
_D5tanya4math8nbtheory2lnFNaNbNiNfeZe:
fldln2 // Put lb(e) onto the FPU stack
fldt 8(%rsp) // Put the argument onto the FPU stack
flds -4(%rsp) // Put a float onto the FPU stack
fyl2x // %st1 * lb(%st0)
// The result is on the FPU stack, but returned in %xmm0
fstps -4(%rsp)
movss -4(%rsp), %xmm0
ret
// log.
.globl _D5tanya4math8nbtheory2lnFNaNbNiNfdZd
.type _D5tanya4math8nbtheory2lnFNaNbNiNfdZd, @function
.globl _D5tanya4math8nbtheory3logFNaNbNiNfdZd
.type _D5tanya4math8nbtheory3logFNaNbNiNfdZd, @function
_D5tanya4math8nbtheory2lnFNaNbNiNfdZd:
_D5tanya4math8nbtheory3logFNaNbNiNfdZd:
movsd %xmm0, -8(%rsp) // Put the argument onto the stack
fldln2 // Put lb(e) onto the FPU stack
@ -30,19 +37,12 @@ _D5tanya4math8nbtheory2lnFNaNbNiNfdZd:
ret
// logf.
.globl _D5tanya4math8nbtheory2lnFNaNbNiNffZf
.type _D5tanya4math8nbtheory2lnFNaNbNiNffZf, @function
_D5tanya4math8nbtheory2lnFNaNbNiNffZf:
movss %xmm0, -4(%rsp) // Put the argument onto the stack
// logl.
.globl _D5tanya4math8nbtheory4loglFNaNbNiNfeZe
.type _D5tanya4math8nbtheory4loglFNaNbNiNfeZe, @function
_D5tanya4math8nbtheory4loglFNaNbNiNfeZe:
fldln2 // Put lb(e) onto the FPU stack
flds -4(%rsp) // Put a float onto the FPU stack
fldt 8(%rsp) // Put the argument onto the FPU stack
fyl2x // %st1 * lb(%st0)
// The result is on the FPU stack, but returned in %xmm0
fstps -4(%rsp)
movss -4(%rsp), %xmm0
ret

View File

@ -1,21 +1,19 @@
.text
/*
* cmpMemory.
* equalMemory.
*
* rdi - r1 length
* rsi - r1 data.
* rdx - r2 length.
* rcx - r2 data.
*/
.globl _D5tanya6memory2op9cmpMemoryFNaNbNixAvxAvZi
.type _D5tanya6memory2op9cmpMemoryFNaNbNixAvxAvZi, @function
_D5tanya6memory2op9cmpMemoryFNaNbNixAvxAvZi:
.globl _D5tanya6memory2op11equalMemoryFNaNbNixAvxQdZb
.type _D5tanya6memory2op11equalMemoryFNaNbNixAvxQdZb, @function
_D5tanya6memory2op11equalMemoryFNaNbNixAvxQdZb:
// Compare the lengths
cmp %rdx, %rdi
jl less
jg greater
jne not_equal
mov %rcx, %rdi
@ -27,8 +25,7 @@ _D5tanya6memory2op9cmpMemoryFNaNbNixAvxAvZi:
naligned:
cmpsb
jl less
jg greater
jne not_equal
dec %rdx
test $0x07, %edi
@ -39,29 +36,24 @@ _D5tanya6memory2op9cmpMemoryFNaNbNixAvxAvZi:
shr $0x03, %rcx
repe cmpsq
jl less
jg greater
jne not_equal
and $0x07, %edx
jz equal
aligned_1: // Compare the remaining bytes
mov %rdx, %rcx
cmp $0x0, %rcx
repe cmpsb
jl less
jg greater
jne not_equal
equal:
mov $0x01, %rax // Return 1
jmp end
not_equal:
xor %rax, %rax // Return 0
jmp end
greater:
mov $0x01, %rax
jmp end
less:
mov $-0x01, %rax
end:
ret

View File

@ -37,6 +37,11 @@ _D5tanya6memory2op10fillMemoryFNaNbNiAvmZv:
mov %rsi, %r8
// If the length is less than the number of misaligned bytes,
// write one byte at a time and exit
cmp %rax, %rcx
jg aligned_1
naligned:
mov %dl, (%r8) // Write a byte

View File

@ -11,10 +11,11 @@ The returned value is placed in %rax.
*/
.text
.globl syscall1
.type syscall1, @function
// 1 parameter.
.globl _D5tanya3sys5linux7syscallQiFNbNillZl
.type _D5tanya3sys5linux7syscallQiFNbNillZl, @function
syscall1:
_D5tanya3sys5linux7syscallQiFNbNillZl:
movq %rsi, %rax // Syscall number.
syscall
@ -22,44 +23,43 @@ syscall1:
ret
.globl syscall2
.type syscall2, @function
// 2 parameters.
.globl _D5tanya3sys5linux7syscallQiFNbNilllZl
.type _D5tanya3sys5linux7syscallQiFNbNilllZl, @function
syscall2:
// Store registers.
movq %rdi, %r8
movq %rdx, %rax // Syscall number.
// Syscall arguments.
movq %rsi, %rdi
movq %r8, %rsi
_D5tanya3sys5linux7syscallQiFNbNilllZl:
movq %rdx, %rax
syscall
// Restore registers.
movq %rdi, %rsi
movq %r8, %rdi
ret
.globl syscall3
.type syscall3, @function
// 3 parameters.
.globl _D5tanya3sys5linux7syscallQiFNbNillllZl
.type _D5tanya3sys5linux7syscallQiFNbNillllZl, @function
syscall3:
// Store registers.
movq %rdi, %r8
movq %rcx, %rax // Syscall number.
// Syscall arguments.
movq %rdx, %rdi
movq %r8, %rdx
_D5tanya3sys5linux7syscallQiFNbNillllZl:
movq %rcx, %rax
syscall
// Restore registers.
movq %r8, %rdi
ret
// 6 parameters.
.globl _D5tanya3sys5linux7syscallQiFNbNilllllllZl
.type _D5tanya3sys5linux7syscallQiFNbNilllllllZl, @function
_D5tanya3sys5linux7syscallQiFNbNilllllllZl:
pushq %rbp
movq %rsp, %rbp
movq 16(%rbp), %rax
mov %rcx, %r10
syscall
leave
ret

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

View File

@ -1,14 +1,36 @@
{
"name": "tanya",
"description": "General purpose, @nogc library. Containers, networking, metaprogramming, 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:sys": "*",
"tanya:os": "*",
"tanya:encoding": "*",
"tanya:middle": "*",
"tanya:test": "*"
},
"dependencies-linux": {
"mir-linux-kernel": "~>1.0.0"
},
"subPackages": [
"./meta",
"./sys",
"./os",
"./encoding",
"./middle",
"./test"
],
"configurations": [
{
"name": "library",
@ -27,6 +49,39 @@
"preBuildCommands": ["ninja -C arch"],
"lflags": ["arch/tanya.a"],
"versions": ["TanyaNative"]
},
{
"name": "unittest",
"versions": ["TanyaPhobos"],
"importPaths": [
"./source",
"./tests"
],
"sourcePaths": [
"./source",
"./tests"
]
},
{
"name": "unittest-native",
"platforms": ["linux-x86_64-gdc"],
"preBuildCommands": ["ninja -C arch"],
"lflags": ["arch/tanya.a"],
"versions": ["TanyaNative"],
"importPaths": [
"./source",
"./tests"
],
"sourcePaths": [
"./source",
"./tests"
]
}
]
],
"dflags-dmd": ["-dip1000"],
"libs-windows": ["advapi32"],
"libs-windows-x86_mscoff": ["iphlpapi"],
"libs-windows-x86_64": ["iphlpapi"]
}

17
encoding/dub.json Normal file
View File

@ -0,0 +1,17 @@
{
"name": "encoding",
"description": "This package provides tools to work with text encodings",
"targetType": "library",
"dependencies": {
"tanya:meta": "*"
},
"sourcePaths": [
"."
],
"importPaths": [
"."
],
"dflags-dmd": ["-dip1000"]
}

View File

@ -8,34 +8,34 @@
* ASCII is $(B A)merican $(B S)tandard $(B C)ode for $(B I)nformation
* $(B I)nterchange.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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/encoding/ascii.d,
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/encoding/tanya/encoding/ascii.d,
* tanya/encoding/ascii.d)
*/
module tanya.encoding.ascii;
import tanya.meta.trait;
const string fullHexDigits = "0123456789ABCDEFabcdef"; /// 0..9A..Fa..f.
const string hexDigits = "0123456789ABCDEF"; /// 0..9A..F.
const string lowerHexDigits = "0123456789abcdef"; /// 0..9a..f.
const string digits = "0123456789"; /// 0..9.
const string octalDigits = "01234567"; /// 0..7.
immutable string fullHexDigits = "0123456789ABCDEFabcdef"; /// 0..9A..Fa..f.
immutable string hexDigits = "0123456789ABCDEF"; /// 0..9A..F.
immutable string lowerHexDigits = "0123456789abcdef"; /// 0..9a..f.
immutable string digits = "0123456789"; /// 0..9.
immutable string octalDigits = "01234567"; /// 0..7.
/// A..Za..z.
const string letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
immutable string letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const string uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; /// A..Z.
const string lowercase = "abcdefghijklmnopqrstuvwxyz"; /// a..z.
immutable string uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; /// A..Z.
immutable string lowercase = "abcdefghijklmnopqrstuvwxyz"; /// a..z.
/**
* Whitespace, Horizontal Tab (HT), Line Feed (LF), Carriage Return (CR),
* Vertical Tab (VT) or Form Feed (FF).
*/
const string whitespace = "\t\n\v\f\r ";
immutable string whitespace = "\t\n\v\f\r ";
/// Letter case specifier.
enum LetterCase : bool
@ -61,7 +61,7 @@ if (isSomeChar!C)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(isUpper('A'));
assert(isUpper('Z'));
@ -87,7 +87,7 @@ if (isSomeChar!C)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(isLower('a'));
assert(isLower('z'));
@ -113,7 +113,7 @@ if (isSomeChar!C)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(isAlpha('A'));
assert(isAlpha('Z'));
@ -141,7 +141,7 @@ if (isSomeChar!C)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(isDigit('0'));
assert(isDigit('1'));
@ -174,7 +174,7 @@ if (isSomeChar!C)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(isAlphaNum('0'));
assert(isAlphaNum('1'));
@ -205,7 +205,7 @@ if (isSomeChar!C)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(isASCII('0'));
assert(isASCII('L'));
@ -240,7 +240,7 @@ if (isSomeChar!C)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(isControl('\t'));
assert(isControl('\0'));
@ -281,7 +281,7 @@ if (isSomeChar!C)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(isWhite('\t'));
assert(isWhite('\n'));
@ -312,7 +312,7 @@ if (isSomeChar!C)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(isGraphical('a'));
assert(isGraphical('0'));
@ -343,7 +343,7 @@ if (isSomeChar!C)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(isPrintable('a'));
assert(isPrintable('0'));
@ -372,7 +372,7 @@ if (isSomeChar!C)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(isHexDigit('0'));
assert(isHexDigit('1'));
@ -403,7 +403,7 @@ if (isSomeChar!C)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(isOctalDigit('0'));
assert(isOctalDigit('1'));
@ -436,7 +436,7 @@ if (isSomeChar!C)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(isPunctuation('!'));
assert(isPunctuation(':'));
@ -459,14 +459,14 @@ pure nothrow @safe @nogc unittest
* Returns: The lowercase of $(D_PARAM c) if available, just $(D_PARAM c)
* otherwise.
*/
C toUpper(C)(const C c)
C toUpper(C)(C c)
if (isSomeChar!C)
{
return isLower(c) ? (cast(C) (c - 32)) : c;
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(toUpper('a') == 'A');
assert(toUpper('A') == 'A');
@ -486,14 +486,14 @@ pure nothrow @safe @nogc unittest
* Returns: The uppercase of $(D_PARAM c) if available, just $(D_PARAM c)
* otherwise.
*/
C toLower(C)(const C c)
C toLower(C)(C c)
if (isSomeChar!C)
{
return isUpper(c) ? (cast(C) (c + 32)) : c;
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
assert(toLower('A') == 'a');
assert(toLower('a') == 'a');

View File

@ -5,11 +5,11 @@
/**
* This package provides tools to work with text encodings.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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/encoding/package.d,
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/encoding/tanya/encoding/package.d,
* tanya/encoding/package.d)
*/
module tanya.encoding;

13
meta/dub.json Normal file
View File

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

View File

@ -9,11 +9,11 @@
* It contains different algorithms for iterating, searching and modifying
* template arguments.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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,
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/meta/tanya/meta/metafunction.d,
* tanya/meta/metafunction.d)
*/
module tanya.meta.metafunction;
@ -44,7 +44,7 @@ import tanya.meta.transform;
* See_Also: $(D_PSYMBOL isLess).
*/
template Min(alias pred, Args...)
if (Args.length > 0 && isTemplate!pred)
if (Args.length > 0 && __traits(isTemplate, pred))
{
static if (Args.length == 1)
{
@ -91,7 +91,7 @@ if (Args.length > 0 && isTemplate!pred)
* See_Also: $(D_PSYMBOL isLess).
*/
template Max(alias pred, Args...)
if (Args.length > 0 && isTemplate!pred)
if (Args.length > 0 && __traits(isTemplate, pred))
{
static if (Args.length == 1)
{
@ -116,7 +116,7 @@ if (Args.length > 0 && isTemplate!pred)
}
/**
* Zips one or more $(D_PSYMBOL Tuple)s with $(D_PARAM f).
* Zips one or more $(D_PSYMBOL Pack)s with $(D_PARAM f).
*
* Given $(D_PARAM f) and tuples t1, t2, ..., tk, where tk[i] denotes the
* $(I i)-th element of the tuple $(I k)-th tuple, $(D_PSYMBOL ZipWith)
@ -129,7 +129,7 @@ if (Args.length > 0 && isTemplate!pred)
* f(tk[0], tk[1], ... tk[i]),
* ---
*
* $(D_PSYMBOL ZipWith) begins with the first elements from $(D_PARAM Tuples)
* $(D_PSYMBOL ZipWith) begins with the first elements from $(D_PARAM Packs)
* and applies $(D_PARAM f) to them, then it takes the second
* ones and does the same, and so on.
*
@ -140,16 +140,17 @@ if (Args.length > 0 && isTemplate!pred)
*
* Params:
* f = Some template that can be applied to the elements of
* $(D_PARAM Tuples).
* Tuples = $(D_PSYMBOL Tuple) instances.
* $(D_PARAM Packs).
* Packs = $(D_PSYMBOL Pack) instances.
*
* Returns: A sequence, whose $(I i)-th element contains the $(I i)-th element
* from each of the $(D_PARAM Tuples).
* from each of the $(D_PARAM Packs).
*/
template ZipWith(alias f, Tuples...)
if (Tuples.length > 0
&& isTemplate!f
&& allSatisfy!(ApplyLeft!(isInstanceOf, Tuple), Tuples))
template ZipWith(alias f, Packs...)
if (Packs.length > 0
&& __traits(isTemplate, f)
&& (allSatisfy!(ApplyLeft!(isInstanceOf, Pack), Packs)
|| allSatisfy!(ApplyLeft!(isInstanceOf, Tuple), Packs)))
{
private template GetIth(size_t i, Args...)
{
@ -164,43 +165,37 @@ if (Tuples.length > 0
}
private template Iterate(size_t i, Args...)
{
alias Tuple = GetIth!(i, Args);
alias Pack = GetIth!(i, Args);
static if (Tuple.length < Tuples.length)
static if (Pack.length < Packs.length)
{
alias Iterate = AliasSeq!();
}
else
{
alias Iterate = AliasSeq!(f!Tuple,
Iterate!(i + 1, Args));
alias Iterate = AliasSeq!(f!Pack, Iterate!(i + 1, Args));
}
}
alias ZipWith = Iterate!(0, Tuples);
alias ZipWith = Iterate!(0, Packs);
}
///
@nogc nothrow pure @safe unittest
{
alias Result1 = ZipWith!(AliasSeq,
Tuple!(1, 2),
Tuple!(5, 6),
Tuple!(9, 10));
alias Result1 = ZipWith!(AliasSeq, Pack!(1, 2), Pack!(5, 6), Pack!(9, 10));
static assert(Result1 == AliasSeq!(1, 5, 9, 2, 6, 10));
alias Result2 = ZipWith!(AliasSeq,
Tuple!(1, 2, 3),
Tuple!(4, 5));
alias Result2 = ZipWith!(AliasSeq, Pack!(1, 2, 3), Pack!(4, 5));
static assert(Result2 == AliasSeq!(1, 4, 2, 5));
alias Result3 = ZipWith!(AliasSeq, Tuple!(), Tuple!(4, 5));
alias Result3 = ZipWith!(AliasSeq, Pack!(), Pack!(4, 5));
static assert(Result3.length == 0);
}
/**
* Holds a typed sequence of template parameters.
*
* Different than $(D_PSYMBOL AliasSeq), $(D_PSYMBOL Tuple) doesn't unpack
* Different than $(D_PSYMBOL AliasSeq), $(D_PSYMBOL Pack) doesn't unpack
* its template parameters automatically. Consider:
*
* ---
@ -215,7 +210,7 @@ if (Tuples.length > 0
* Using $(D_PSYMBOL AliasSeq) template `A` gets 4 parameters instead of 2,
* because $(D_PSYMBOL AliasSeq) is just an alias for its template parameters.
*
* With $(D_PSYMBOL Tuple) it is possible to pass distinguishable
* With $(D_PSYMBOL Pack) it is possible to pass distinguishable
* sequences of parameters to a template. So:
*
* ---
@ -224,29 +219,31 @@ if (Tuples.length > 0
* static assert(Args.length == 2);
* }
*
* alias BInstance = B!(Tuple!(int, uint), Tuple!(float, double));
* alias BInstance = B!(Pack!(int, uint), Pack!(float, double));
* ---
*
* Params:
* Args = Elements of this $(D_PSYMBOL Tuple).
* Args = Elements of this $(D_PSYMBOL Pack).
*
* See_Also: $(D_PSYMBOL AliasSeq).
*/
template Tuple(Args...)
struct Pack(Args...)
{
/// Elements in this tuple as $(D_PSYMBOL AliasSeq).
alias Seq = Args;
/// The length of the tuple.
enum size_t length = Args.length;
alias Seq this;
}
///
@nogc nothrow pure @safe unittest
{
alias A = Tuple!(short);
alias B = Tuple!(3, 8, 9);
alias C = Tuple!(A, B);
alias A = Pack!short;
alias B = Pack!(3, 8, 9);
alias C = Pack!(A, B);
static assert(C.length == 2);
@ -255,7 +252,7 @@ template Tuple(Args...)
static assert(B.length == 3);
static assert(B.Seq == AliasSeq!(3, 8, 9));
alias D = Tuple!();
alias D = Pack!();
static assert(D.length == 0);
static assert(is(D.Seq == AliasSeq!()));
}
@ -263,20 +260,22 @@ template Tuple(Args...)
/**
* Unordered sequence of unique aliases.
*
* $(D_PARAM Args) can contain duplicates, but they will be filteredout, so
* $(D_PARAM Args) can contain duplicates, but they will be filtered out, so
* $(D_PSYMBOL Set) contains only unique items. $(D_PSYMBOL isEqual) is used
* for determining if two items are equal.
*
* Params:
* Args = Elements of this $(D_PSYMBOL Tuple).
* Args = Elements of this $(D_PSYMBOL Set).
*/
template Set(Args...)
struct Set(Args...)
{
/// Elements in this set as $(D_PSYMBOL AliasSeq).
alias Seq = NoDuplicates!Args;
/// The length of the set.
enum size_t length = Seq.length;
alias Seq this;
}
///
@ -444,7 +443,7 @@ if (isInstanceOf!(Set, S1) && isInstanceOf!(Set, S2))
* to $(D_INLINECODE Args[1]), $(D_KEYWORD false) otherwise.
*/
template isLessEqual(alias cmp, Args...)
if (Args.length == 2 && isTemplate!cmp)
if (Args.length == 2 && __traits(isTemplate, cmp))
{
private enum result = cmp!(Args[1], Args[0]);
static if (is(typeof(result) == bool))
@ -491,7 +490,7 @@ if (Args.length == 2 && isTemplate!cmp)
* equal to $(D_INLINECODE Args[1]), $(D_KEYWORD false) otherwise.
*/
template isGreaterEqual(alias cmp, Args...)
if (Args.length == 2 && isTemplate!cmp)
if (Args.length == 2 && __traits(isTemplate, cmp))
{
private enum result = cmp!Args;
static if (is(typeof(result) == bool))
@ -538,7 +537,7 @@ if (Args.length == 2 && isTemplate!cmp)
* $(D_INLINECODE Args[1]), $(D_KEYWORD false) otherwise.
*/
template isLess(alias cmp, Args...)
if (Args.length == 2 && isTemplate!cmp)
if (Args.length == 2 && __traits(isTemplate, cmp))
{
private enum result = cmp!Args;
static if (is(typeof(result) == bool))
@ -585,7 +584,7 @@ if (Args.length == 2 && isTemplate!cmp)
* $(D_INLINECODE Args[1]), $(D_KEYWORD false) otherwise.
*/
template isGreater(alias cmp, Args...)
if (Args.length == 2 && isTemplate!cmp)
if (Args.length == 2 && __traits(isTemplate, cmp))
{
private enum result = cmp!Args;
static if (is(typeof(result) == bool))
@ -633,7 +632,7 @@ if (Args.length == 2)
{
static if ((is(typeof(Args[0] == Args[1])) && (Args[0] == Args[1]))
|| (isTypeTuple!Args && is(Args[0] == Args[1]))
|| isSame!Args)
|| __traits(isSame, Args[0], Args[1]))
{
enum bool isEqual = true;
}
@ -682,7 +681,7 @@ if (Args.length == 2)
}
/**
* Instantiates the template $(D_PARAM T) with $(D_PARAM ARGS).
* Instantiates the template $(D_PARAM T) with $(D_PARAM Args).
*
* Params:
* T = Template.
@ -799,22 +798,7 @@ alias AliasSeq(Args...) = Args;
* 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...)
if (isTemplate!F)
{
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;
}
}
enum bool allSatisfy(alias F, L...) = Filter!(templateNot!F, L).length == 0;
///
@nogc nothrow pure @safe unittest
@ -837,22 +821,7 @@ if (isTemplate!F)
* 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...)
if (isTemplate!F)
{
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 .. $]);
}
}
enum bool anySatisfy(alias F, L...) = Filter!(F, L).length != 0;
///
@nogc nothrow pure @safe unittest
@ -862,22 +831,19 @@ if (isTemplate!F)
static assert(!anySatisfy!(isSigned, uint, ushort, ulong));
}
private template indexOf(ptrdiff_t i, Args...)
if (Args.length > 0)
private template indexOf(Args...)
{
static if (Args.length == 1)
static foreach (i, Arg; Args[1 .. $])
{
static if (!is(typeof(indexOf) == ptrdiff_t) && isEqual!(Args[0], Arg))
{
enum ptrdiff_t indexOf = i;
}
}
static if (!is(typeof(indexOf) == ptrdiff_t))
{
enum ptrdiff_t indexOf = -1;
}
else static if (isEqual!(Args[0 .. 2]))
{
enum ptrdiff_t indexOf = i;
}
else
{
enum ptrdiff_t indexOf = indexOf!(i + 1,
AliasSeq!(Args[0], Args[2 .. $]));
}
}
/**
@ -892,13 +858,13 @@ if (Args.length > 0)
*/
template staticIndexOf(T, L...)
{
enum ptrdiff_t staticIndexOf = indexOf!(0, AliasSeq!(T, L));
enum ptrdiff_t staticIndexOf = indexOf!(T, L);
}
/// ditto
template staticIndexOf(alias T, L...)
{
enum ptrdiff_t staticIndexOf = indexOf!(0, AliasSeq!(T, L));
enum ptrdiff_t staticIndexOf = indexOf!(T, L);
}
///
@ -921,16 +887,10 @@ template staticIndexOf(alias T, L...)
* Returns: $(D_KEYWORD true) if $(D_PARAM T) can be found in $(D_PARAM L),
* $(D_KEYWORD false) otherwise.
*/
template canFind(T, L...)
{
enum bool canFind = indexOf!(0, AliasSeq!(T, L)) != -1;
}
enum bool canFind(T, L...) = staticIndexOf!(T, L) != -1;
/// ditto
template canFind(alias T, L...)
{
enum bool canFind = indexOf!(0, AliasSeq!(T, L)) != -1;
}
enum bool canFind(alias T, L...) = staticIndexOf!(T, L) != -1;
///
@nogc nothrow pure @safe unittest
@ -941,6 +901,32 @@ template canFind(alias T, L...)
static assert(canFind!(3, () {}, uint, 5, 3));
}
/*
* Tests whether $(D_PARAM T) is a template.
*
* $(D_PSYMBOL isTemplate) isn't $(D_KEYWORD true) for template instances,
* since the latter already represent some type. Only not instantiated
* templates, i.e. that accept some template parameters, are considered
* templates.
*
* Params:
* T = A symbol.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM T) is a template,
* $(D_KEYWORD false) otherwise.
*/
private enum bool isTemplate(alias T) = __traits(isTemplate, T);
///
@nogc nothrow pure @safe unittest
{
static struct S(T)
{
}
static assert(isTemplate!S);
static assert(!isTemplate!(S!int));
}
/**
* Combines multiple templates with logical AND. So $(D_PSYMBOL templateAnd)
* evaluates to $(D_INLINECODE Preds[0] && Preds[1] && Preds[2]) and so on.
@ -1045,7 +1031,7 @@ if (allSatisfy!(isTemplate, Preds))
* Returns: Negated $(D_PARAM pred).
*/
template templateNot(alias pred)
if (isTemplate!pred)
if (__traits(isTemplate, pred))
{
enum bool templateNot(T...) = !pred!T;
}
@ -1079,7 +1065,7 @@ if (isTemplate!pred)
* if not.
*/
template isSorted(alias cmp, L...)
if (isTemplate!cmp)
if (__traits(isTemplate, cmp))
{
static if (L.length <= 1)
{
@ -1104,30 +1090,6 @@ if (isTemplate!cmp)
static assert(!isSorted!(cmp, long, byte, ubyte, short, uint));
}
@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));
}
/**
* Params:
* T = A template.
@ -1355,7 +1317,7 @@ template Reverse(L...)
* Returns: Elements $(D_PARAM T) after applying $(D_PARAM F) to them.
*/
template Map(alias F, T...)
if (isTemplate!F)
if (__traits(isTemplate, F))
{
static if (T.length == 0)
{
@ -1397,7 +1359,7 @@ if (isTemplate!F)
* See_Also: $(LINK2 https://en.wikipedia.org/wiki/Merge_sort, Merge sort).
*/
template Sort(alias cmp, L...)
if (isTemplate!cmp)
if (__traits(isTemplate, cmp))
{
private template merge(size_t A, size_t B)
{
@ -1616,6 +1578,7 @@ template EraseAll(alias T, L...)
* $(D_PARAM pred).
*/
template Filter(alias pred, L...)
if (__traits(isTemplate, pred))
{
static if (L.length == 0)
{
@ -1634,7 +1597,8 @@ template Filter(alias pred, L...)
///
@nogc nothrow pure @safe unittest
{
static assert(is(Filter!(isIntegral, real, int, bool, uint) == AliasSeq!(int, uint)));
alias Given = AliasSeq!(real, int, bool, uint);
static assert(is(Filter!(isIntegral, Given) == AliasSeq!(int, uint)));
}
/**
@ -1661,8 +1625,8 @@ template NoDuplicates(L...)
///
@nogc nothrow pure @safe unittest
{
alias Types = AliasSeq!(int, uint, int, short, short, uint);
static assert(is(NoDuplicates!Types == AliasSeq!(int, uint, short)));
alias Given = AliasSeq!(int, uint, int, short, short, uint);
static assert(is(NoDuplicates!Given == AliasSeq!(int, uint, short)));
}
/**
@ -1775,3 +1739,62 @@ if (T.length == 2)
static assert(is(Select!(true, int, float) == int));
static assert(is(Select!(false, int, float) == float));
}
/**
* Attaches a numeric index to each element from $(D_PARAM Args).
*
* $(D_PSYMBOL EnumerateFrom) returns a sequence of tuples ($(D_PSYMBOL Pack)s)
* consisting of the index of each element and the element itself.
*
* Params:
* start = Enumeration initial value.
* Args = Enumerated sequence.
*
* See_Also: $(D_PSYMBOL Enumerate).
*/
template EnumerateFrom(size_t start, Args...)
{
static if (Args.length == 0)
{
alias EnumerateFrom = AliasSeq!();
}
else
{
alias EnumerateFrom = AliasSeq!(Pack!(start, Args[0]), EnumerateFrom!(start + 1, Args[1 .. $]));
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(EnumerateFrom!(0, int, uint, bool).length == 3);
}
///
@nogc nothrow pure @safe unittest
{
alias Expected = AliasSeq!(Pack!(cast(size_t) 0, int),
Pack!(cast(size_t) 1, uint));
static assert(is(EnumerateFrom!(0, int, uint) == Expected));
}
/**
* Attaches a numeric index to each element from $(D_PARAM Args).
*
* $(D_PSYMBOL EnumerateFrom) returns a sequence of tuples ($(D_PSYMBOL Pack)s)
* consisting of the index of each element and the element itself.
*
* Params:
* Args = Enumerated sequence.
*
* See_Also: $(D_PSYMBOL EnumerateFrom).
*/
alias Enumerate(Args...) = EnumerateFrom!(0, Args);
///
@nogc nothrow pure @safe unittest
{
alias Expected = AliasSeq!(Pack!(cast(size_t) 0, int),
Pack!(cast(size_t) 1, uint));
static assert(is(Enumerate!(int, uint) == Expected));
}

View File

@ -9,11 +9,11 @@
* to transform from one type to another. It has also different algorithms for
* iterating, searching and modifying template arguments.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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;

View File

@ -8,11 +8,11 @@
* Templates in this module are used to obtain type information at compile
* time.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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/trait.d,
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/meta/tanya/meta/trait.d,
* tanya/meta/trait.d)
*/
module tanya.meta.trait;
@ -70,45 +70,6 @@ enum bool isWideString(T) = is(T : const dchar[]) && !isStaticArray!T;
static assert(!isWideString!(dchar[10]));
}
/**
* 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)));
}
/**
* Determines whether $(D_PARAM T) is a complex type.
*
@ -150,132 +111,7 @@ enum bool isComplex(T) = is(Unqual!(OriginalType!T) == cfloat)
static assert(!isComplex!real);
}
/**
* POD (Plain Old Data) is a $(D_KEYWORD struct) without constructors,
* destructors and member functions.
*
* Params:
* T = A type.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM T) is a POD type,
* $(D_KEYWORD false) otherwise.
*/
enum bool isPOD(T) = __traits(isPOD, T);
///
@nogc nothrow pure @safe unittest
{
struct S1
{
void method()
{
}
}
static assert(!isPOD!S1);
struct S2
{
void function() val; // Function pointer, not a member function.
}
static assert(isPOD!S2);
struct S3
{
this(this)
{
}
}
static assert(!isPOD!S3);
}
/**
* Returns size of the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: Size of the type $(D_PARAM T).
*/
enum size_t sizeOf(T) = T.sizeof;
///
@nogc nothrow pure @safe unittest
{
static assert(sizeOf!(bool function()) == size_t.sizeof);
static assert(sizeOf!bool == 1);
static assert(sizeOf!short == 2);
static assert(sizeOf!int == 4);
static assert(sizeOf!long == 8);
static assert(sizeOf!(void[16]) == 16);
}
/**
* Returns the alignment of the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: Alignment of the type $(D_PARAM T).
*/
enum size_t alignOf(T) = T.alignof;
///
@nogc nothrow pure @safe unittest
{
static assert(alignOf!bool == bool.alignof);
static assert(is(typeof(alignOf!bool) == typeof(bool.alignof)));
}
/**
* Tests whether $(D_INLINECODE Args[0]) and $(D_INLINECODE Args[1]) are the
* same symbol.
*
* Params:
* Args = Two symbols to be tested.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM Args) are the same symbol,
* $(D_KEYWORD false) otherwise.
*/
template isSame(Args...)
if (Args.length == 2)
{
enum bool isSame = __traits(isSame, Args[0], Args[1]);
}
///
@nogc nothrow pure @safe unittest
{
static assert(isSame!("string", "string"));
static assert(!isSame!(string, immutable(char)[]));
}
/**
* Tests whether $(D_PARAM T) is a template.
*
* $(D_PSYMBOL isTemplate) isn't $(D_KEYWORD true) for template instances,
* since the latter already represent some type. Only not instantiated
* templates, i.e. that accept some template parameters, are considered
* templates.
*
* Params:
* T = A symbol.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM T) is a template,
* $(D_KEYWORD false) otherwise.
*/
enum bool isTemplate(alias T) = __traits(isTemplate, T);
///
@nogc nothrow pure @safe unittest
{
struct S(T)
{
}
static assert(isTemplate!S);
static assert(!isTemplate!(S!int));
}
/**
/*
* Tests whether $(D_PARAM T) is an interface.
*
* Params:
@ -284,29 +120,7 @@ enum bool isTemplate(alias T) = __traits(isTemplate, T);
* Returns: $(D_KEYWORD true) if $(D_PARAM T) is an interface,
* $(D_KEYWORD false) otherwise.
*/
enum bool isInterface(T) = is(T == interface);
/**
* Tests whether $(D_PARAM T) is a class.
*
* Params:
* T = A type.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM T) is a class,
* $(D_KEYWORD false) otherwise.
*/
enum bool isClass(T) = is(T == class);
/**
* Tests whether $(D_PARAM T) is a struct.
*
* Params:
* T = A type.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM T) is a struct,
* $(D_KEYWORD false) otherwise.
*/
enum bool isStruct(T) = is(T == struct);
private enum bool isInterface(T) = is(T == interface);
/**
* Determines whether $(D_PARAM T) is a polymorphic type, i.e. a
@ -332,6 +146,9 @@ enum bool isPolymorphicType(T) = is(T == class) || is(T == interface);
}
/**
* Determines whether the type $(D_PARAM T) has a static method
* named $(D_PARAM member).
*
* Params:
* T = Aggregate type.
* member = Symbol name.
@ -364,7 +181,7 @@ template hasStaticMember(T, string member)
///
@nogc nothrow pure @safe unittest
{
struct S
static struct S
{
int member1;
void member2()
@ -588,14 +405,14 @@ enum bool isBoolean(T) = is(Unqual!(OriginalType!T) == bool);
}
static assert(isBoolean!E);
struct S1
static struct S1
{
bool b;
alias b this;
}
static assert(!isBoolean!S1);
struct S2
static struct S2
{
bool opCast(T : bool)()
{
@ -715,7 +532,7 @@ template isPointer(T)
{
static if (is(T U : U*))
{
enum bool isPointer = true;
enum bool isPointer = !is(Unqual!(OriginalType!T) == typeof(null));
}
else
{
@ -913,7 +730,7 @@ enum bool isAggregateType(T) = is(T == struct)
///
@nogc nothrow pure @safe unittest
{
struct S;
static struct S;
class C;
interface I;
union U;
@ -1078,45 +895,6 @@ template mostNegative(T)
static assert(mostNegative!cfloat == -cfloat.max);
}
/**
* 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)));
}
/**
* Determines whether the type $(D_PARAM T) is copyable.
*
@ -1135,20 +913,20 @@ enum bool isCopyable(T) = is(typeof({ T s1 = T.init; T s2 = s1; }));
///
@nogc nothrow pure @safe unittest
{
struct S1
static struct S1
{
}
struct S2
static struct S2
{
this(this)
{
}
}
struct S3
static struct S3
{
@disable this(this);
}
struct S4
static struct S4
{
S3 s;
}
@ -1210,8 +988,19 @@ enum bool isAbstractClass(T) = __traits(isAbstractClass, T);
static assert(!isAbstractClass!E);
}
private enum bool isType(alias T) = is(T);
private enum bool isType(T) = true;
/**
* Checks whether $(D_PARAM T) is a type, same as `is(T)` does.
*
* Params:
* T = A symbol.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM T) is a type, $(D_KEYWORD false)
* otherwise.
*/
enum bool isType(alias T) = is(T);
/// ditto
enum bool isType(T) = true;
/**
* Determines whether $(D_PARAM Args) contains only types.
@ -1243,7 +1032,7 @@ enum bool isTypeTuple(Args...) = allSatisfy!(isType, Args);
union U
{
}
struct T()
static struct T()
{
}
@ -1653,7 +1442,7 @@ if (F.length == 1)
///
@nogc nothrow pure @safe unittest
{
struct S
static struct S
{
void opCall()
{
@ -1676,21 +1465,9 @@ if (F.length == 1)
static assert(!isCallable!I);
}
@nogc nothrow pure @safe unittest
{
struct S
{
@property int opCall()
{
return 0;
}
}
S s;
static assert(isCallable!S);
static assert(isCallable!s);
}
/**
* Determines whether $(D_PARAM T) defines a symbol $(D_PARAM member).
*
* Params:
* T = Aggregate type.
* member = Symbol name.
@ -1703,7 +1480,7 @@ enum bool hasMember(T, string member) = __traits(hasMember, T, member);
///
@nogc nothrow pure @safe unittest
{
struct S
static struct S
{
int member1;
void member2()
@ -1758,7 +1535,7 @@ template isMutable(T)
///
@nogc nothrow pure @safe unittest
{
struct S
static struct S
{
void method()
{
@ -1799,7 +1576,7 @@ if (is(T == class) || is(T == struct) || is(T == union))
}
///
pure nothrow @safe unittest
@nogc pure nothrow @safe unittest
{
static struct S
{
@ -1872,63 +1649,6 @@ if (isCallable!F)
static assert(is(FunctionTypeOf!(() {}) == function));
}
@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));
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
{
struct S2
{
@property int opCall()
{
return 0;
}
}
S2 s2;
static assert(is(FunctionTypeOf!S2 == function));
static assert(is(FunctionTypeOf!s2 == function));
}
/**
* Determines the return type of the callable $(D_PARAM F).
*
@ -1970,20 +1690,20 @@ alias TemplateOf(alias T : Base!Args, alias Base, Args...) = Base;
///
@nogc nothrow pure @safe unittest
{
struct S(T)
static struct S(T)
{
}
static assert(isSame!(TemplateOf!(S!int), S));
static assert(__traits(isSame, TemplateOf!(S!int), S));
static void func(T)()
{
}
static assert(isSame!(TemplateOf!(func!int), func));
static assert(__traits(isSame, TemplateOf!(func!int), func));
template T(U)
{
}
static assert(isSame!(TemplateOf!(T!int), T));
static assert(__traits(isSame, TemplateOf!(T!int), T));
}
/**
@ -2015,7 +1735,7 @@ template isInstanceOf(alias T, alias I)
{
static if (is(typeof(TemplateOf!I)))
{
enum bool isInstanceOf = isSame!(TemplateOf!I, T);
enum bool isInstanceOf = __traits(isSame, TemplateOf!I, T);
}
else
{
@ -2026,7 +1746,7 @@ template isInstanceOf(alias T, alias I)
///
@nogc nothrow pure @safe unittest
{
struct S(T)
static struct S(T)
{
}
static assert(isInstanceOf!(S, S!int));
@ -2285,23 +2005,22 @@ template isAssignable(Lhs, Rhs = Lhs)
///
@nogc nothrow pure @safe unittest
{
struct S1
static struct S1
{
@disable this();
@disable this(this);
}
struct S2
static struct S2
{
void opAssign(S1 s) pure nothrow @safe @nogc
{
}
}
struct S3
static struct S3
{
void opAssign(ref S1 s) pure nothrow @safe @nogc
{
}
}
static assert(isAssignable!(S2, S1));
static assert(!isAssignable!(S3, S1));
@ -2538,6 +2257,61 @@ if (isCallable!F)
static assert((functionAttributes!func1 & FunctionAttribute.return_) == 0);
}
/**
* Determines whether a function has attribute.
*
* This template should get at least two arguments: the function itself and the
* attributes it should be tested for. If more than one attribute is given,
* $(D_PSYMBOL hasFunctionAttributes) evaluates to $(D_KEYWORD true) if all of
* them are present. The attributes should be $(D_PSYMBOL FunctionAttribute)
* members.
*
* Params:
* Args = The function and attributes.
*
* Returns:
*
* See_Also: $(D_PSYMBOL FunctionAttribute).
*/
template hasFunctionAttributes(Args...)
if (Args.length > 1
&& is(typeof(Args[1]) == FunctionAttribute)
&& isCallable!(Args[0])
&& allSameType!(Map!(TypeOf, Args[1 .. $])))
{
enum uint pred(Args_...) = Args_[0] | Args_[1];
template Reduce(Args_...)
{
static if (Args_.length == 1)
{
enum uint Reduce = Args_[0];
}
else
{
enum uint Reduce = Reduce!(pred!(Args_[0], Args_[1]), Args_[2 .. $]);
}
}
enum uint field = Reduce!(0, Args[1 .. $]);
enum hasFunctionAttributes = (functionAttributes!(Args[0]) & field) == field;
}
///
@nogc nothrow pure @safe unittest
{
static struct Range
{
@property auto front() inout
{
return 8;
}
}
static assert(hasFunctionAttributes!(Range.init.front, FunctionAttribute.inout_));
static assert(!hasFunctionAttributes!(Range.init.front, FunctionAttribute.const_));
static assert(!hasFunctionAttributes!(Range.init.front,
FunctionAttribute.inout_, FunctionAttribute.const_));
}
/**
* Returns a tuple with default values of the parameters to $(D_PARAM F).
*
@ -2749,44 +2523,6 @@ template hasElaborateAssign(T)
}
}
@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);
}
/**
* Returns all members of $(D_KEYWORD enum) $(D_PARAM T).
*
@ -2812,14 +2548,23 @@ if (is(T == enum))
}
else
{
alias getEnumMembers = AliasSeq!(__traits(getMember, T, Args[0]), getEnumMembers!(Args[1 .. $]));
alias getEnumMembers = AliasSeq!(__traits(getMember, T, Args[0]),
getEnumMembers!(Args[1 .. $]));
}
}
alias EnumMembers = getEnumMembers!(__traits(allMembers, T));
private alias allMembers = AliasSeq!(__traits(allMembers, T));
static if (allMembers.length == 1)
{
alias EnumMembers = AliasSeq!(__traits(getMember, T, allMembers));
}
else
{
alias EnumMembers = getEnumMembers!allMembers;
}
}
///
pure nothrow @nogc @safe unittest
@nogc nothrow pure @safe unittest
{
enum E : int
{
@ -2827,7 +2572,7 @@ pure nothrow @nogc @safe unittest
two,
three,
}
static assert([E.one, E.two, E.three] == [ EnumMembers!E ]);
static assert([EnumMembers!E] == [E.one, E.two, E.three]);
}
/**
@ -2856,7 +2601,7 @@ if (is(T == class))
}
static assert(classInstanceAlignment!C1 == C1.alignof);
struct S
static struct S
{
align(8)
uint s;
@ -2870,6 +2615,58 @@ if (is(T == class))
static assert(classInstanceAlignment!C2 == S.alignof);
}
/**
* Returns the size in bytes of the state that needs to be allocated to hold an
* object of type $(D_PARAM T).
*
* There is a difference between the `.sizeof`-property and
* $(D_PSYMBOL stateSize) if $(D_PARAM T) is a class or an interface.
* `T.sizeof` is constant on the given architecture then and is the same as
* `size_t.sizeof` and `ptrdiff_t.sizeof`. This is because classes and
* interfaces are reference types and `.sizeof` returns the size of the
* reference which is the same as the size of a pointer. $(D_PSYMBOL stateSize)
* returns the size of the instance itself.
*
* The size of a dynamic array is `size_t.sizeof * 2` since a dynamic array
* stores its length and a data pointer. The size of the static arrays is
* calculated differently since they are value types. It is the array length
* multiplied by the element size.
*
* `stateSize!void` is `1` since $(D_KEYWORD void) is mostly used as a synonym
* for $(D_KEYWORD byte)/$(D_KEYWORD ubyte) in `void*`.
*
* Params:
* T = Object type.
*
* Returns: Size of an instance of type $(D_PARAM T).
*/
template stateSize(T)
{
static if (isPolymorphicType!T)
{
enum size_t stateSize = __traits(classInstanceSize, T);
}
else
{
enum size_t stateSize = T.sizeof;
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(stateSize!int == 4);
static assert(stateSize!bool == 1);
static assert(stateSize!(int[]) == (size_t.sizeof * 2));
static assert(stateSize!(short[3]) == 6);
static struct Empty
{
}
static assert(stateSize!Empty == 1);
static assert(stateSize!void == 1);
}
/**
* Tests whether $(D_INLINECODE pred(T)) can be used as condition in an
* $(D_KEYWORD if)-statement or a ternary operator.
@ -2896,12 +2693,12 @@ template ifTestable(T, alias pred = a => a)
{
static assert(ifTestable!int);
struct S1
static struct S1
{
}
static assert(!ifTestable!S1);
struct S2
static struct S2
{
bool opCast(T : bool)()
{
@ -2968,7 +2765,7 @@ alias getUDAs(alias symbol) = AliasSeq!(__traits(getAttributes, symbol));
///
@nogc nothrow pure @safe unittest
{
struct Attr
static struct Attr
{
int i;
}
@ -2987,7 +2784,7 @@ alias getUDAs(alias symbol) = AliasSeq!(__traits(getAttributes, symbol));
static assert(getUDAs!(c, "String").length == 0);
static assert(getUDAs!(c, 4).length == 0);
struct T(U)
static struct T(U)
{
enum U s = 7;
U i;
@ -3023,10 +2820,10 @@ template hasUDA(alias symbol, alias attr)
///
@nogc nothrow pure @safe unittest
{
struct Attr1
static struct Attr1
{
}
struct Attr2
static struct Attr2
{
}
@Attr1 int a;
@ -3034,6 +2831,46 @@ template hasUDA(alias symbol, alias attr)
static assert(!hasUDA!(a, Attr2));
}
/**
* If $(D_PARAM T) is a type, constructs its default value, otherwise
* $(D_PSYMBOL evalUDA) aliases itself to $(D_PARAM T).
*
* This template is useful when working with UDAs with default parameters,
* i.e. if an attribute can be given as `@Attr` or `@Attr("param")`,
* $(D_PSYMBOL evalUDA) makes `@Attr()` from `@Attr`, but returns
* `@Attr("param")` as is.
*
* $(D_PARAM T) (or its type if it isn't a type already) should have a default
* constructor.
*
* Params:
* T = User Defined Attribute.
*/
alias evalUDA(alias T) = T;
/// ditto
alias evalUDA(T) = Alias!(T());
///
@nogc nothrow pure @safe unittest
{
static struct Length
{
size_t length = 8;
}
@Length @Length(0) int i;
alias uda = AliasSeq!(__traits(getAttributes, i));
alias attr1 = evalUDA!(uda[0]);
alias attr2 = evalUDA!(uda[1]);
static assert(is(typeof(attr1) == Length));
static assert(is(typeof(attr2) == Length));
static assert(attr1.length == 8);
static assert(attr2.length == 0);
}
/**
* Tests whether $(D_PARAM T) is an inner class, i.e. a class nested inside
* another class.
@ -3082,14 +2919,6 @@ template isInnerClass(T)
static assert(!isInnerClass!(O.Fake));
}
@nogc nothrow pure @safe unittest
{
class RefCountedStore(T)
{
}
static assert(!isInnerClass!(RefCountedStore!int));
}
/**
* Returns the types of all members of $(D_PARAM T).
*
@ -3146,3 +2975,74 @@ template Fields(T)
static assert(is(Fields!short == AliasSeq!short));
}
/**
* Determines whether all $(D_PARAM Types) are the same.
*
* If $(D_PARAM Types) is empty, returns $(D_KEYWORD true).
*
* Params:
* Types = Type sequence.
*
* Returns: $(D_KEYWORD true) if all $(D_PARAM Types) are the same,
* $(D_KEYWORD false) otherwise.
*/
template allSameType(Types...)
{
static if (Types.length == 0)
{
enum bool allSameType = true;
}
else
{
private enum bool sameType(T) = is(T == Types[0]);
enum bool allSameType = allSatisfy!(sameType, Types[1 .. $]);
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(allSameType!());
static assert(allSameType!int);
static assert(allSameType!(int, int, int));
static assert(!allSameType!(int, uint, int));
static assert(!allSameType!(int, uint, short));
}
/**
* Determines whether values of type $(D_PARAM T) can be compared for equality,
* i.e. using `==` or `!=` binary operators.
*
* Params:
* T = Type to test.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM T) can be compared for equality,
* $(D_KEYWORD false) otherwise.
*/
enum bool isEqualityComparable(T) = ifTestable!(T, a => a == a);
///
@nogc nothrow pure @safe unittest
{
static assert(isEqualityComparable!int);
}
/**
* Determines whether values of type $(D_PARAM T) can be compared for ordering,
* i.e. using `>`, `>=`, `<` or `<=` binary operators.
*
* Params:
* T = Type to test.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM T) can be compared for ordering,
* $(D_KEYWORD false) otherwise.
*/
enum bool isOrderingComparable(T) = ifTestable!(T, a => a > a);
///
@nogc nothrow pure @safe unittest
{
static assert(isOrderingComparable!int);
}

View File

@ -9,15 +9,16 @@
* 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-2019.
* 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;
/**
@ -42,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;
}
@ -137,8 +138,6 @@ template OriginalType(T)
* To = Target type.
*
* Returns: $(D_PARAM To) with the constness of $(D_PARAM From).
*
* See_Also: $(D_PSYMBOL CopyTypeQualifiers).
*/
template CopyConstness(From, To)
{
@ -184,187 +183,6 @@ template CopyConstness(From, To)
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;
}
}
///
@nogc nothrow pure @safe 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;
}
}
///
@nogc nothrow pure @safe 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;
}
}
///
@nogc nothrow pure @safe 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*`.
*
@ -444,37 +262,6 @@ template ValueType(T)
static assert(!is(ValueType!(int[15])));
}
/**
* Params:
* T = Scalar type.
*
* Returns: The type $(D_PARAM T) will promote to.
*
* See_Also: $(LINK2 https://dlang.org/spec/type.html#integer-promotions,
* Integer Promotions).
*/
template Promoted(T)
if (isScalarType!T)
{
alias Promoted = CopyTypeQualifiers!(T, typeof(T.init + T.init));
}
///
@nogc nothrow pure @safe unittest
{
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));
}
/**
* Adds $(D_KEYWORD inout) qualifier to the type $(D_PARAM T).
*
@ -603,86 +390,6 @@ alias SharedInoutConstOf(T) = shared(inout const T);
static assert(is(SharedInoutConstOf!int == shared inout const int));
}
/**
* Returns a template with one argument which applies all qualifiers of
* $(D_PARAM T) on its argument if instantiated.
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE shared(inout const T)).
*/
template QualifierOf(T)
{
static if (is(T U == const U))
{
alias QualifierOf = ConstOf;
}
else static if (is(T U == immutable U))
{
alias QualifierOf = ImmutableOf;
}
else static if (is(T U == inout U))
{
alias QualifierOf = InoutOf;
}
else static if (is(T U == inout const U))
{
alias QualifierOf = InoutConstOf;
}
else static if (is(T U == shared U))
{
alias QualifierOf = SharedOf;
}
else static if (is(T U == shared const U))
{
alias QualifierOf = SharedConstOf;
}
else static if (is(T U == shared inout U))
{
alias QualifierOf = SharedInoutOf;
}
else static if (is(T U == shared inout const U))
{
alias QualifierOf = SharedInoutConstOf;
}
else
{
alias QualifierOf(T) = T;
}
}
///
@nogc nothrow pure @safe unittest
{
alias MutableOf = QualifierOf!int;
static assert(is(MutableOf!uint == uint));
alias ConstOf = QualifierOf!(const int);
static assert(is(ConstOf!uint == const uint));
alias InoutOf = QualifierOf!(inout int);
static assert(is(InoutOf!uint == inout uint));
alias InoutConstOf = QualifierOf!(inout const int);
static assert(is(InoutConstOf!uint == inout const uint));
alias ImmutableOf = QualifierOf!(immutable int);
static assert(is(ImmutableOf!uint == immutable uint));
alias SharedOf = QualifierOf!(shared int);
static assert(is(SharedOf!uint == shared uint));
alias SharedConstOf = QualifierOf!(shared const int);
static assert(is(SharedConstOf!uint == shared const uint));
alias SharedInoutOf = QualifierOf!(shared inout int);
static assert(is(SharedInoutOf!uint == shared inout uint));
alias SharedInoutConstOf = QualifierOf!(shared inout const int);
static assert(is(SharedInoutConstOf!uint == shared inout const uint));
}
/**
* Determines the type of $(D_PARAM T). If $(D_PARAM T) is already a type,
* $(D_PSYMBOL TypeOf) aliases itself to $(D_PARAM T).
@ -701,7 +408,7 @@ alias TypeOf(T) = T;
/// ditto
template TypeOf(alias T)
if (isExpressions!T || isTemplate!T)
if (isExpressions!T || __traits(isTemplate, T))
{
alias TypeOf = typeof(T);
}
@ -717,3 +424,81 @@ if (isExpressions!T || isTemplate!T)
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)));
}

23
middle/dub.json Normal file
View File

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

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-2019.
* 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 std.algorithm.iteration;
import std.algorithm.mutation;
import tanya.conv;
import tanya.exception;
public import tanya.memory.allocator;
import tanya.memory.mmappool;
import tanya.memory.lifetime;
import tanya.meta.trait;
import tanya.range.primitive;
/**
* 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,8 @@ mixin template DefaultAllocator()
*
* Precondition: $(D_INLINECODE allocator_ !is null)
*/
this(shared Allocator allocator) pure nothrow @safe @nogc
in
{
assert(allocator !is null);
}
body
this(shared Allocator allocator) @nogc nothrow pure @safe
in (allocator !is null)
{
this.allocator_ = allocator;
}
@ -58,12 +114,8 @@ mixin template DefaultAllocator()
*
* Postcondition: $(D_INLINECODE allocator !is null)
*/
protected @property shared(Allocator) allocator() pure nothrow @safe @nogc
out (allocator)
{
assert(allocator !is null);
}
body
@property shared(Allocator) allocator() @nogc nothrow pure @safe
out (allocator; allocator !is null)
{
if (allocator_ is null)
{
@ -73,12 +125,8 @@ mixin template DefaultAllocator()
}
/// ditto
@property shared(Allocator) allocator() const pure nothrow @trusted @nogc
out (allocator)
{
assert(allocator !is null);
}
body
@property shared(Allocator) allocator() const @nogc nothrow pure @trusted
out (allocator; allocator !is null)
{
if (allocator_ is null)
{
@ -88,19 +136,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
{
allocator = MmapPool.instance;
}
private shared(Allocator) getAllocatorInstance() nothrow @nogc
private shared(Allocator) getAllocatorInstance() @nogc nothrow
{
if (allocator is null)
{
version (TanyaNative)
{
import tanya.memory.mmappool : MmapPool;
defaultAllocator = MmapPool.instance;
}
else
{
import tanya.memory.mallocator : Mallocator;
defaultAllocator = Mallocator.instance;
}
}
return allocator;
}
@ -109,12 +161,8 @@ private shared(Allocator) getAllocatorInstance() nothrow @nogc
*
* Postcondition: $(D_INLINECODE allocator !is null).
*/
@property shared(Allocator) defaultAllocator() pure nothrow @trusted @nogc
out (allocator)
{
assert(allocator !is null);
}
body
@property shared(Allocator) defaultAllocator() @nogc nothrow pure @trusted
out (allocator; allocator !is null)
{
return (cast(GetPureInstance!Allocator) &getAllocatorInstance)();
}
@ -127,68 +175,12 @@ body
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
@property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc
in
{
assert(allocator !is null);
}
body
@property void defaultAllocator(shared(Allocator) allocator) @nogc nothrow @safe
in (allocator !is null)
{
.allocator = allocator;
}
/**
* Returns the size in bytes of the state that needs to be allocated to hold an
* object of type $(D_PARAM T).
*
* There is a difference between the `.sizeof`-property and
* $(D_PSYMBOL stateSize) if $(D_PARAM T) is a class or an interface.
* `T.sizeof` is constant on the given architecture then and is the same as
* `size_t.sizeof` and `ptrdiff_t.sizeof`. This is because classes and
* interfaces are reference types and `.sizeof` returns the size of the
* reference which is the same as the size of a pointer. $(D_PSYMBOL stateSize)
* returns the size of the instance itself.
*
* The size of a dynamic array is `size_t.sizeof * 2` since a dynamic array
* stores its length and a data pointer. The size of the static arrays is
* calculated differently since they are value types. It is the array length
* multiplied by the element size.
*
* `stateSize!void` is `1` since $(D_KEYWORD void) is mostly used as a synonym
* for $(D_KEYWORD byte)/$(D_KEYWORD ubyte) in `void*`.
*
* Params:
* T = Object type.
*
* Returns: Size of an instance of type $(D_PARAM T).
*/
template stateSize(T)
{
static if (isPolymorphicType!T)
{
enum size_t stateSize = __traits(classInstanceSize, T);
}
else
{
enum size_t stateSize = T.sizeof;
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(stateSize!int == 4);
static assert(stateSize!bool == 1);
static assert(stateSize!(int[]) == (size_t.sizeof * 2));
static assert(stateSize!(short[3]) == 6);
static struct Empty
{
}
static assert(stateSize!Empty == 1);
static assert(stateSize!void == 1);
}
/**
* Params:
* size = Raw size.
@ -202,71 +194,178 @@ 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.
*
* 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).
/**
* Error thrown if memory allocation fails.
*/
package(tanya) T[] resize(T)(shared Allocator allocator,
auto ref T[] array,
const size_t length) @trusted
final class OutOfMemoryError : Error
{
if (length == 0)
/**
* Constructs new error.
*
* 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 = "Out of memory",
string file = __FILE__,
size_t line = __LINE__,
Throwable next = null) @nogc nothrow pure @safe
{
if (allocator.deallocate(array))
{
return null;
}
else
{
onOutOfMemoryError();
}
super(msg, file, line, next);
}
void[] buf = array;
if (!allocator.reallocate(buf, length * T.sizeof))
/// ditto
this(string msg,
Throwable next,
string file = __FILE__,
size_t line = __LINE__) @nogc nothrow pure @safe
{
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)
{
() @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 (allocator !is null)
{
auto mem = (() @trusted => allocator.allocate(stateSize!T))();
if (mem is null)
{
onOutOfMemoryError();
}
// Casting from void[] is unsafe, but we know we cast to the original type.
array = cast(T[]) buf;
scope (failure)
{
() @trusted { allocator.deallocate(mem); }();
}
return array;
return emplace!T(mem[0 .. stateSize!T], args);
}
private unittest
/**
* 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 (allocator !is null)
{
int[] p;
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);
}
p = defaultAllocator.resize(p, 20);
assert(p.length == 20);
///
@nogc nothrow pure @safe unittest
{
int* i = defaultAllocator.make!int(5);
assert(*i == 5);
defaultAllocator.dispose(i);
}
p = defaultAllocator.resize(p, 30);
assert(p.length == 30);
/**
* 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 (allocator !is null)
in (n <= size_t.max / E.sizeof)
{
auto ret = allocator.resize!E(null, n);
p = defaultAllocator.resize(p, 10);
assert(p.length == 10);
static if (hasElaborateDestructor!E)
{
for (auto range = ret; range.length != 0; range = range[1 .. $])
{
emplace!E(cast(void[]) range[0 .. 1], E.init);
}
}
else
{
ret[] = E.init;
}
p = defaultAllocator.resize(p, 0);
assert(p is null);
return ret;
}
///
@nogc nothrow pure @safe unittest
{
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);
@ -274,8 +373,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)
{
@ -329,173 +428,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
// From druntime
extern (C)
private void _d_monitordelete(Object h, bool det) @nogc nothrow pure;
/*
* 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 = 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).
*/
package(tanya) T[] resize(T)(shared Allocator allocator,
auto ref T[] array,
const size_t length) @trusted
{
struct S
if (length == 0)
{
~this()
if (allocator.deallocate(array))
{
return null;
}
else
{
onOutOfMemoryError();
}
}
auto p = cast(S[]) defaultAllocator.allocate(S.sizeof);
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).
*
* 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);
}
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;
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)
{
onOutOfMemoryError();
}
scope (failure)
{
() @trusted { allocator.deallocate(mem); }();
}
return emplace!T(mem[0 .. stateSize!T], 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,508 @@
/* 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.
* 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 (memory.length >= stateSize!T)
out (result; memory.ptr is (() @trusted => cast(void*) result)())
{
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 (memory.length == stateSize!T)
out (result; memory.ptr is (() @trusted => cast(void*) result)())
{
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 (memory.length >= T.sizeof)
out (result; memory.ptr is result)
{
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 (memory.length >= T.sizeof)
out (result; memory.ptr is result)
{
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 (&source !is &target, "Source and target must be different")
{
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,18 +3,22 @@
* 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-2019.
* 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;
version (TanyaPhobos):
version (TanyaNative)
{
}
else:
import core.stdc.stdlib;
import tanya.memory.allocator;
@ -26,11 +30,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.
@ -40,7 +44,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)
{
@ -52,7 +56,7 @@ final class Mallocator : Allocator
}
///
@nogc nothrow unittest
@nogc nothrow pure @system unittest
{
auto p = Mallocator.instance.allocate(20);
assert(p.length == 20);
@ -70,7 +74,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)
{
@ -80,7 +84,7 @@ final class Mallocator : Allocator
}
///
@nogc nothrow unittest
@nogc nothrow pure @system unittest
{
void[] p;
assert(Mallocator.instance.deallocate(p));
@ -98,14 +102,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));
@ -120,7 +125,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)
{
@ -149,7 +155,7 @@ final class Mallocator : Allocator
}
///
@nogc nothrow unittest
@nogc nothrow pure @system unittest
{
void[] p;
@ -166,30 +172,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)
{
@ -210,13 +201,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,87 +2,46 @@
* 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-2019.
* 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 std.algorithm.comparison;
version (TanyaNative):
import mir.linux._asm.unistd;
import tanya.memory.allocator;
import tanya.memory.op;
import tanya.os.error;
import tanya.sys.linux.syscall;
import tanya.sys.posix.mman;
version (Posix)
private void* mapMemory(const size_t length) @nogc nothrow pure @system
{
import core.sys.posix.sys.mman : MAP_ANON,
MAP_FAILED,
MAP_PRIVATE,
PROT_READ,
PROT_WRITE;
import core.sys.posix.unistd;
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)
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;
}
auto p = syscall_(0,
length,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1,
0,
NR_mmap);
return p == -ErrorCode.noMemory ? null : cast(void*) p;
}
/**
private bool unmapMemory(shared void* addr, const size_t length)
@nogc nothrow pure @system
{
return syscall_(cast(ptrdiff_t) addr, length, NR_munmap) == 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
@ -110,7 +69,7 @@ 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))
{
@ -126,7 +85,7 @@ final class MmapPool : Allocator
}
}
/**
/*
* Allocates $(D_PARAM size) bytes of memory.
*
* Params:
@ -134,7 +93,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)
{
@ -155,35 +114,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.
@ -193,7 +123,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)
@ -235,7 +166,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)
@ -245,7 +176,7 @@ final class MmapPool : Allocator
block.next = block.next.next;
}
/**
/*
* Deallocates a memory block.
*
* Params:
@ -253,7 +184,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)
{
@ -299,15 +230,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
@ -319,8 +242,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)
{
@ -383,32 +306,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:
@ -417,7 +315,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)
{
@ -441,7 +340,7 @@ final class MmapPool : Allocator
}
if (p !is null)
{
copy(p[0 .. min(p.length, size)], reallocP);
copy(p[0 .. p.length < size ? p.length : size], reallocP);
deallocate(p);
}
p = reallocP;
@ -449,87 +348,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)
{
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.
*
@ -539,12 +386,10 @@ 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;
@ -591,9 +436,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);
}
/*
@ -602,43 +448,38 @@ 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_;
}
/*
* Params:
* x = Required space.
* pageSize = Page size.
* x = Required space.
*
* 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
{
@ -661,60 +502,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_
- BlockEntry.sizeof * 2
- RegionEntry.sizeof
- 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);
}

View File

@ -5,11 +5,11 @@
/**
* Set of operations on memory blocks.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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,
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/middle/tanya/memory/op.d,
* tanya/memory/op.d)
*/
module tanya.memory.op;
@ -24,7 +24,7 @@ version (TanyaNative)
extern private void moveMemory(const void[], void[])
pure nothrow @system @nogc;
extern private int cmpMemory(const void[], const void[])
extern private bool equalMemory(const void[], const void[])
pure nothrow @system @nogc;
}
else
@ -52,13 +52,9 @@ private enum alignMask = size_t.sizeof - 1;
* 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);
}
body
in (source.length <= target.length)
in (source.length == 0 || source.ptr !is null)
in (target.length == 0 || target.ptr !is null)
{
version (TanyaNative)
{
@ -76,27 +72,7 @@ body
ubyte[9] source = [1, 2, 3, 4, 5, 6, 7, 8, 9];
ubyte[9] target;
source.copy(target);
assert(cmp(source, target) == 0);
}
@nogc nothrow pure @safe 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);
}
assert(equal(source, target));
}
/*
@ -122,11 +98,7 @@ private template filledBytes(ubyte Byte, ubyte I = 0)
* memory = Memory block.
*/
void fill(ubyte c = 0)(void[] memory) @trusted
in
{
assert(memory.length == 0 || memory.ptr !is null);
}
body
in (memory.length == 0 || memory.ptr !is null)
{
version (TanyaNative)
{
@ -149,31 +121,6 @@ body
}
}
// Stress test. Checks that `fill` can handle unaligned pointers and different
// lengths.
@nogc nothrow pure @safe 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).
@ -197,13 +144,9 @@ body
* 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);
}
body
in (source.length <= target.length)
in (source.length == 0 || source.ptr !is null)
in (target.length == 0 || target.ptr !is null)
{
version (TanyaNative)
{
@ -222,85 +165,7 @@ body
ubyte[6] expected = [ 'a', 'a', 'a', 'a', 'b', 'b' ];
copyBackward(mem[0 .. 4], mem[2 .. $]);
assert(cmp(expected, mem) == 0);
}
@nogc nothrow pure @safe 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) @nogc nothrow pure @trusted
in
{
assert(r1.length == 0 || r1.ptr !is null);
assert(r2.length == 0 || r2.ptr !is null);
}
body
{
version (TanyaNative)
{
return cmpMemory(r1, r2);
}
else
{
if (r1.length > r2.length)
{
return 1;
}
return r1.length < r2.length ? -1 : memcmp(r1.ptr, r2.ptr, r1.length);
}
}
///
@nogc nothrow pure @safe 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);
}
@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(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);
assert(equal(expected, mem));
}
/**
@ -315,13 +180,9 @@ body
* 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, const ubyte needle)
inout(void[]) find(return inout void[] haystack, ubyte needle)
@nogc nothrow pure @trusted
in
{
assert(haystack.length == 0 || haystack.ptr !is null);
}
body
in (haystack.length == 0 || haystack.ptr !is null)
{
auto length = haystack.length;
const size_t needleWord = size_t.max * needle;
@ -336,19 +197,19 @@ body
{
return bytes[0 .. length];
}
bytes++;
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)
if ((((*words ^ needleWord) - highBits) & (~*words) & mask) != 0)
{
break;
}
words++;
++words;
length -= size_t.sizeof;
}
@ -360,8 +221,8 @@ body
{
return bytes[0 .. length];
}
bytes++;
length--;
++bytes;
--length;
}
return haystack[$ .. $];
@ -372,14 +233,119 @@ body
{
const ubyte[9] haystack = ['a', 'b', 'c', 'd', 'e', 'f', 'b', 'g', 'h'];
assert(find(haystack, 'a') == haystack[]);
assert(find(haystack, 'b') == haystack[1 .. $]);
assert(find(haystack, 'c') == haystack[2 .. $]);
assert(find(haystack, 'd') == haystack[3 .. $]);
assert(find(haystack, 'e') == haystack[4 .. $]);
assert(find(haystack, 'f') == haystack[5 .. $]);
assert(find(haystack, 'h') == haystack[8 .. $]);
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 (haystack.length == 0 || haystack.ptr !is null)
{
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 (r1.length == 0 || r1.ptr !is null)
in (r2.length == 0 || r2.ptr !is null)
{
version (TanyaNative)
{
return equalMemory(r1, r2);
}
else
{
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

@ -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/. */
/**
* Dynamic memory management.
*
* Copyright: Eugene Wissner 2016-2019.
* 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/package.d,
* tanya/memory/package.d)
*/
module tanya.memory;
public import tanya.memory.allocator;
public import tanya.memory.lifetime;
deprecated("Use tanya.meta.trait.stateSize instead")
public import tanya.meta.trait : stateSize;

View File

@ -14,26 +14,22 @@
* $(LI Unique ownership)
* )
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2019.
* 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 std.algorithm.comparison;
import tanya.algorithm.mutation;
import tanya.conv;
import tanya.exception;
import tanya.memory;
import tanya.memory.allocator;
import tanya.memory.lifetime;
import tanya.meta.trait;
import tanya.range.primitive;
private template Payload(T)
{
static if (isPolymorphicType!T || isArray!T)
static if (isPolymorphicType!T || isDynamicArray!T)
{
alias Payload = T;
}
@ -50,11 +46,7 @@ private final class RefCountedStore(T)
size_t opUnary(string op)()
if (op == "--" || op == "++")
in
{
assert(this.counter > 0);
}
body
in (this.counter > 0)
{
mixin("return " ~ op ~ "counter;");
}
@ -135,11 +127,7 @@ struct RefCounted(T)
/// ditto
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
in (allocator !is null)
{
this.allocator_ = allocator;
}
@ -243,11 +231,7 @@ struct RefCounted(T)
* Precondition: $(D_INLINECODE cound > 0).
*/
inout(Payload!T) get() inout
in
{
assert(count > 0, "Attempted to access an uninitialized reference");
}
body
in (count > 0, "Attempted to access an uninitialized reference")
{
return this.storage.payload;
}
@ -259,7 +243,7 @@ struct RefCounted(T)
* reference types like classes, that can be accessed directly.
*
* Params:
* op = Operation.
* op = Operation.
*
* Returns: Reference to the pointed value.
*/
@ -276,7 +260,7 @@ struct RefCounted(T)
}
/**
* Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal
* Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal
* storage.
*/
@property bool isInitialized() const
@ -305,174 +289,14 @@ struct RefCounted(T)
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);
}
@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)));
}
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;
}
}
}
@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 @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 nothrow pure @safe 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);
}
/**
@ -489,7 +313,7 @@ version (unittest)
* 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 RefCounted!T).
*
* Precondition: $(D_INLINECODE allocator !is null)
@ -497,11 +321,7 @@ version (unittest)
RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
if (!is(T == interface) && !isAbstractClass!T
&& !isAssociativeArray!T && !isArray!T)
in
{
assert(allocator !is null);
}
body
in (allocator !is null)
{
auto rc = typeof(return)(allocator);
@ -530,23 +350,19 @@ 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);
}
body
in (allocator !is null)
in (size <= size_t.max / E.sizeof)
{
return RefCounted!T(allocator.make!T(size), allocator);
}
@ -573,59 +389,6 @@ body
assert(rc.count == 1);
}
@nogc @system 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);
}
}
@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
{
static bool destroyed;
static struct F
{
~this() @nogc nothrow @safe
{
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,11 +423,7 @@ struct Unique(T)
/// ditto
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
in (allocator !is null)
{
this.allocator_ = allocator;
}
@ -743,7 +502,7 @@ struct Unique(T)
* reference types like classes, that can be accessed directly.
*
* Params:
* op = Operation.
* op = Operation.
*
* Returns: Reference to the pointed value.
*/
@ -837,7 +596,7 @@ struct Unique(T)
* 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 Unique!T).
*
* Precondition: $(D_INLINECODE allocator !is null)
@ -845,11 +604,7 @@ struct Unique(T)
Unique!T unique(T, A...)(shared Allocator allocator, auto ref A args)
if (!is(T == interface) && !isAbstractClass!T
&& !isAssociativeArray!T && !isArray!T)
in
{
assert(allocator !is null);
}
body
in (allocator !is null)
{
auto payload = allocator.make!(T, A)(args);
return Unique!T(payload, allocator);
@ -861,63 +616,20 @@ 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
in (allocator !is null)
in (size <= size_t.max / E.sizeof)
{
assert(allocator !is null);
assert(size <= size_t.max / ElementType!T.sizeof);
}
body
{
auto payload = allocator.resize!(ElementType!T)(null, size);
auto payload = allocator.resize!E(null, size);
return Unique!T(payload, allocator);
}
@nogc nothrow pure @safe unittest
{
static assert(is(typeof(defaultAllocator.unique!B(5))));
static assert(is(typeof(defaultAllocator.unique!(int[])(5))));
}
@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);
}

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-2019.
* 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);
@ -241,7 +290,7 @@ struct ErrorCode
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
ErrorCode ec = ErrorCode.fault;
auto errorNo = cast(ErrorCode.ErrorNo) ec;
@ -258,38 +307,38 @@ 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
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);
ErrorCode ec;
assert(ec == ErrorCode.success);
ec = ErrorCode.fault;
assert(ec == ErrorCode.fault);
}
{
auto ec1 = ErrorCode(ErrorCode.fault);
ErrorCode ec2;
assert(ec2 == ErrorCode.success);
ec = ErrorCode.fault;
assert(ec == ErrorCode.fault);
}
ec2 = ec1;
assert(ec1 == ec2);
}
///
@nogc nothrow pure @safe unittest
{
auto ec1 = ErrorCode(ErrorCode.fault);
ErrorCode ec2;
assert(ec2 == ErrorCode.success);
ec2 = ec1;
assert(ec1 == ec2);
}
/**
@ -300,37 +349,62 @@ 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
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;
ErrorCode ec1 = ErrorCode.fault;
ErrorCode ec2 = ErrorCode.accessDenied;
assert(ec1 != ec2);
assert(ec1 != ErrorCode.accessDenied);
assert(ErrorCode.fault != ec2);
}
{
ErrorCode ec1 = ErrorCode.fault;
ErrorCode ec2 = ErrorCode.fault;
assert(ec1 != ec2);
assert(ec1 != ErrorCode.accessDenied);
assert(ErrorCode.fault != ec2);
}
assert(ec1 == ec2);
assert(ec1 == ErrorCode.fault);
assert(ErrorCode.fault == ec2);
///
@nogc nothrow pure @safe unittest
{
ErrorCode ec1 = ErrorCode.fault;
ErrorCode ec2 = ErrorCode.fault;
assert(ec1 == ec2);
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-2019.
* 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,375 @@
/* 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 for comparing values.
*
* Copyright: Eugene Wissner 2018-2019.
* 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/comparison.d,
* tanya/algorithm/comparison.d)
*/
module tanya.algorithm.comparison;
import std.traits : CommonType;
import tanya.algorithm.mutation;
import tanya.math;
static import tanya.memory.op;
import tanya.meta.metafunction;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range.array;
import tanya.range.primitive;
private ref inout(Args[0]) minMax(alias cmp, Args...)(ref inout Args args)
{
auto actual = ((ref arg) @trusted => &arg)(args[0]);
foreach (i, arg; args[1 .. $])
{
static if (isFloatingPoint!(Args[0]))
{
if (isNaN(arg))
{
continue;
}
if (isNaN(*actual))
{
actual = ((ref arg) @trusted => &arg)(args[i + 1]);
continue;
}
}
if (cmp(arg, *actual))
{
actual = ((ref arg) @trusted => &arg)(args[i + 1]);
}
}
return *actual;
}
private T moveIf(T)(ref T arg)
{
static if (hasElaborateCopyConstructor!T && isMutable!T)
{
return move(arg);
}
else
{
return arg;
}
}
/**
* Finds the smallest element in the argument list or a range.
*
* If a range is passed, $(D_PSYMBOL min) returns a range of the same type,
* whose front element is the smallest in the range. If more than one element
* fulfills this condition, the front of the returned range points to
* the first one found.
* If $(D_PARAM range) is empty, the original range is returned.
*
* If $(D_PARAM Args) are floating point numbers, $(B NaN) is not considered
* for comparison. $(B NaN) is returned only if all arguments are $(B NaN)s.
*
* Params:
* Args = Types of the arguments. All arguments should have the same type.
* Range = Forward range type.
* args = Argument list.
* range = Forward range.
*
* Returns: The smallest element.
*/
CommonType!Args min(Args...)(Args args)
if (Args.length >= 2
&& isOrderingComparable!(Args[0])
&& allSameType!(Map!(Unqual, Args)))
{
return moveIf(minMax!((ref a, ref b) => a < b)(args));
}
/// ditto
ref inout(Unqual!(Args[0])) min(Args...)(ref inout Args args)
if (Args.length >= 2
&& isOrderingComparable!(Args[0])
&& allSameType!(Map!(Unqual, Args)))
{
return minMax!((ref a, ref b) => a < b)(args);
}
/// ditto
Range min(Range)(Range range)
if (isForwardRange!Range && isOrderingComparable!(ElementType!Range))
{
if (range.empty)
{
return range;
}
auto actual = range.save;
range.popFront();
for (; !range.empty; range.popFront())
{
if (range.front < actual.front)
{
actual = range.save;
}
}
return actual;
}
///
@nogc nothrow pure @safe unittest
{
assert(min(1, 2) == 1);
assert(min(3, 2) == 2);
assert(min(3, 1, 2) == 1);
int[4] range = [3, 1, 1, 2];
auto minElement = min(range[]);
assert(minElement.front == 1);
assert(minElement.length == 3);
}
/**
* Finds the largest element in the argument list or a range.
*
* If a range is passed, $(D_PSYMBOL max) returns a range of the same type,
* whose front element is the largest in the range. If more than one element
* fulfills this condition, the front of the returned range points to
* the first one found.
* If $(D_PARAM range) is empty, the original range is returned.
*
* If $(D_PARAM Args) are floating point numbers, $(B NaN) is not considered
* for comparison. $(B NaN) is returned only if all arguments are $(B NaN)s.
*
* Params:
* Args = Types of the arguments. All arguments should have the same type.
* Range = Forward range type.
* args = Argument list.
* range = Forward range.
*
* Returns: The largest element.
*/
CommonType!Args max(Args...)(Args args)
if (Args.length >= 2
&& isOrderingComparable!(Args[0])
&& allSameType!(Map!(Unqual, Args)))
{
return moveIf(minMax!((ref a, ref b) => a > b)(args));
}
/// ditto
ref inout(Unqual!(Args[0])) max(Args...)(ref inout Args args)
if (Args.length >= 2
&& isOrderingComparable!(Args[0])
&& allSameType!(Map!(Unqual, Args)))
{
return minMax!((ref a, ref b) => a > b)(args);
}
/// ditto
Range max(Range)(Range range)
if (isForwardRange!Range && isOrderingComparable!(ElementType!Range))
{
if (range.empty)
{
return range;
}
auto actual = range.save;
range.popFront();
for (; !range.empty; range.popFront())
{
if (range.front > actual.front)
{
actual = range.save;
}
}
return actual;
}
///
@nogc nothrow pure @safe unittest
{
assert(max(1, 2) == 2);
assert(max(3, 2) == 3);
assert(max(1, 3, 2) == 3);
int[4] range = [1, 5, 5, 2];
auto maxElement = max(range[]);
assert(maxElement.front == 5);
assert(maxElement.length == 3);
}
/**
* Compares element-wise two ranges for equality.
*
* If the ranges have different lengths, they aren't equal.
*
* Params:
* pred = Predicate used to compare individual element pairs.
* R1 = First range type.
* R2 = Second range type.
* r1 = First range.
* r2 = Second range.
*
* Returns: $(D_KEYWORD true) if both ranges are equal, $(D_KEYWORD false)
* otherwise.
*/
bool equal(alias pred = (auto ref a, auto ref b) => a == b, R1, R2)
(R1 r1, R2 r2)
if (allSatisfy!(isInputRange, R1, R2)
&& is(typeof(pred(r1.front, r2.front)) == bool))
{
static if (isDynamicArray!R1
&& is(R1 == R2)
&& __traits(isPOD, ElementType!R1))
{
return tanya.memory.op.equal(r1, r2);
}
else
{
static if (hasLength!R1 && hasLength!R2)
{
if (r1.length != r2.length)
{
return false;
}
}
for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront())
{
if (!pred(r1.front, r2.front))
{
return false;
}
}
static if (hasLength!R1 && hasLength!R2)
{
return true;
}
else
{
return r1.empty && r2.empty;
}
}
}
///
@nogc nothrow pure @safe unittest
{
int[2] range1 = [1, 2];
assert(equal(range1[], range1[]));
int[3] range2 = [1, 2, 3];
assert(!equal(range1[], range2[]));
}
/**
* Compares element-wise two ranges for ordering.
*
* $(D_PSYMBOL compare) returns a negative value if $(D_PARAM r1) is less than
* $(D_PARAM r2), a positive value if $(D_PARAM r2) is less than $(D_PARAM r1),
* or `0` if $(D_PARAM r1) and $(D_PARAM r2) equal.
*
* $(D_PSYMBOL compare) iterates both ranges in lockstep. Whichever of them
* contains an element that is greater than the respective element at the same
* position in the other range is the greater one of the two.
*
* If one of the ranges becomes empty when iterating, but all elements equal so
* far, the range with more elements is the greater one.
*
* If $(D_PARAM pred) is given, it is used for comparison. $(D_PARAM pred) is
* called as $(D_INLINECODE pred(r1.front, r2.front)) and
* $(D_INLINECODE pred(r2.front, r1.front)) to perform three-way comparison.
* $(D_PARAM pred) should return a $(D_KEYWORD bool).
*
* If $(D_PARAM pred) is not given, but the element type of $(D_PARAM R1)
* defines `opCmp()` for the element type of $(D_PARAM R2), `opCmp()` is used.
*
* Otherwise the comparison is perfomed using the basic comparison operators.
*
* Params:
* pred = Predicate used for comparison.
* R1 = First range type.
* R2 = Second range type.
* r1 = First range.
* r2 = Second range.
*
* Returns: A negative value if $(D_PARAM r1) is less than $(D_PARAM r2), a
* positive value if $D(_PARAM r2) is less than $(D_PARAM r1), `0`
* otherwise.
*/
int compare(alias pred, R1, R2)(R1 r1, R2 r2)
if (allSatisfy!(isInputRange, R1, R2)
&& is(typeof(pred(r1.front, r2.front)) == bool)
&& is(typeof(pred(r2.front, r1.front)) == bool))
{
alias predImpl = (ref r1, ref r2) {
return pred(r2.front, r1.front) - pred(r1.front, r2.front);
};
return compareImpl!(predImpl, R1, R2)(r1, r2);
}
/// ditto
int compare(R1, R2)(R1 r1, R2 r2)
if (allSatisfy!(isInputRange, R1, R2)
&& is(typeof(r1.front < r2.front || r2.front < r1.front)))
{
static if (is(typeof(r1.front.opCmp(r2.front)) == int))
{
alias pred = (ref r1, ref r2) => r1.front.opCmp(r2.front);
}
else
{
alias pred = (ref r1, ref r2) {
return (r2.front < r1.front) - (r1.front < r2.front);
};
}
return compareImpl!(pred, R1, R2)(r1, r2);
}
///
@nogc nothrow pure @safe unittest
{
assert(compare("abc", "abc") == 0);
assert(compare("abcd", "abc") > 0);
assert(compare("ab", "abc") < 0);
assert(compare("abc", "abcd") < 0);
assert(compare("abc", "ab") > 0);
assert(compare("aec", "abc") > 0);
assert(compare("aac", "abc") < 0);
assert(compare("abc", "aec") < 0);
assert(compare("abc", "aab") > 0);
assert(compare("aacd", "abc") < 0);
assert(compare("abc", "aacd") > 0);
assert(compare!((a, b) => a > b)("aec", "abc") < 0);
assert(compare!((a, b) => a > b)("aac", "abc") > 0);
}
private int compareImpl(alias pred, R1, R2)(ref R1 r1, ref R2 r2)
{
for (; !r1.empty || !r2.empty; r1.popFront(), r2.popFront())
{
if (r1.empty)
{
return -1;
}
else if (r2.empty)
{
return 1;
}
const comparison = pred(r1, r2);
if (comparison != 0)
{
return comparison;
}
}
return 0;
}

View File

@ -0,0 +1,841 @@
/* 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-2019.
* 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 tanya.algorithm.comparison;
import tanya.memory.lifetime;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range;
import tanya.typecons;
// These predicates are used to help preserve `const` and `inout` for
// ranges built on other ranges.
private enum hasInoutFront(T) = is(typeof((inout ref T a) => a.front));
private enum hasInoutBack(T) = is(typeof((inout ref T a) => a.back));
private enum hasInoutIndex(T) = is(typeof((inout ref T a, size_t i) => a[i]));
private enum hasConstEmpty(T) = is(typeof(((const T* a) => (*a).empty)(null)) : bool);
private enum hasConstLength(T) = is(typeof(((const T* a) => (*a).length)(null)) : size_t);
private enum hasConstSave(T) = is(typeof(((const T* a) => (*a).save())(null)) : T);
private enum hasConstSlice(T) = is(typeof(((const T* a) => (*a)[0 .. $])(null)) : T);
@nogc nothrow pure @safe unittest
{
// Test the definitions.
static assert(hasInoutFront!string);
static assert(hasInoutBack!string);
static assert(hasInoutIndex!string);
static assert(hasConstEmpty!string);
static assert(hasConstLength!string);
static assert(hasConstSave!string);
static assert(hasConstSlice!string);
// Test that Take propagates const/inout correctly.
alias TakeString = Take!(string, false);
static assert(hasInoutFront!TakeString);
static assert(hasInoutBack!TakeString);
static assert(hasInoutIndex!TakeString);
static assert(hasConstEmpty!TakeString);
static assert(hasConstLength!TakeString);
static assert(hasConstSave!TakeString);
static assert(hasConstSlice!TakeString);
// Test that Retro propagates const/inout correctly.
alias RetroString = Retro!string;
static assert(hasInoutFront!RetroString);
static assert(hasInoutBack!RetroString);
static assert(hasInoutIndex!RetroString);
static assert(hasConstEmpty!RetroString);
static assert(hasConstLength!RetroString);
static assert(hasConstSave!RetroString);
static assert(hasConstSlice!RetroString);
}
private struct Take(R, bool exactly)
{
private R source;
size_t length_;
@disable this();
private this(R source, size_t length)
{
this.source = source;
static if (!exactly && hasLength!R)
{
this.length_ = min(source.length, length);
}
else
{
this.length_ = length;
}
}
mixin(`@property auto ref front() ` ~ (hasInoutFront!R ? `inout ` : ``) ~
`in (!empty)
{
return this.source.front;
}`);
void popFront()
in (!empty)
{
this.source.popFront();
--this.length_;
}
mixin(`@property bool empty() ` ~ (exactly || isInfinite!R || hasConstEmpty!R ? `const ` : ``) ~
`{
static if (exactly || isInfinite!R)
{
return length == 0;
}
else
{
return this.length_ == 0 || this.source.empty;
}
}`);
static if (exactly || hasLength!R)
{
@property size_t length() const
{
return this.length_;
}
}
static if (hasAssignableElements!R)
{
@property void front(ref ElementType!R value)
in (!empty)
{
this.source.front = value;
}
@property void front(ElementType!R value)
in (!empty)
{
this.source.front = move(value);
}
}
static if (isForwardRange!R)
{
mixin(`typeof(this) save() ` ~ (hasConstSave!R ? `const ` : ``) ~
`{
return typeof(this)(this.source.save(), length);
}`);
}
static if (isRandomAccessRange!R)
{
mixin(`@property auto ref back() ` ~ (hasInoutBack!R ? `inout ` : ``) ~
`in (!empty)
{
return this.source[this.length - 1];
}`);
void popBack()
in (!empty)
{
--this.length_;
}
mixin(`auto ref opIndex(size_t i) ` ~ (hasInoutIndex!R ? `inout ` : ``) ~
`in (i < length)
{
return this.source[i];
}`);
static if (hasAssignableElements!R)
{
@property void back(ref ElementType!R value)
in (!empty)
{
this.source[length - 1] = value;
}
@property void back(ElementType!R value)
in (!empty)
{
this.source[length - 1] = move(value);
}
void opIndexAssign(ref ElementType!R value, size_t i)
in (i < length)
{
this.source[i] = value;
}
void opIndexAssign(ElementType!R value, size_t i)
in (i < length)
{
this.source[i] = move(value);
}
}
}
static if (!exactly && hasSlicing!R)
{
static if (is(typeof(length))) alias opDollar = length;
mixin(`auto opSlice(size_t i, size_t j) ` ~ (hasConstSlice!R ? `const ` : ``) ~
`in (i <= j)
in (j <= length)
{
return typeof(this)(this.source[i .. j], length);
}`);
}
version (unittest) static assert(isInputRange!Take);
}
/**
* Takes $(D_PARAM n) elements from $(D_PARAM range).
*
* If $(D_PARAM range) doesn't have $(D_PARAM n) elements, the resulting range
* spans all elements of $(D_PARAM range).
*
* $(D_PSYMBOL take) is particulary useful with infinite ranges. You can take
` $(B n) elements from such range and pass the result to an algorithm which
* expects a finit range.
*
* Params:
* R = Type of the adapted range.
* range = The range to take the elements from.
* n = The number of elements to take.
*
* Returns: A range containing maximum $(D_PARAM n) first elements of
* $(D_PARAM range).
*
* See_Also: $(D_PSYMBOL takeExactly).
*/
auto take(R)(R range, size_t n)
if (isInputRange!R)
{
static if (hasSlicing!R && hasLength!R)
{
if (range.length <= n)
return range;
else
return range[0 .. n];
}
// Special case: take(take(...), n)
else static if (is(Range == Take!(RRange, exact), RRange, bool exact))
{
if (n > range.length_)
n = range.length_;
static if (exact)
// `take(takeExactly(r, n0), n)` is rewritten `takeExactly(r, min(n0, n))`.
return Take!(RRange, true)(range.source, n);
else
// `take(take(r, n0), n)` is rewritten `take(r, min(n0, n))`.
return Take!(RRange, false)(range.source, n);
}
else static if (isInfinite!R)
{
// If the range is infinite then `take` is the same as `takeExactly`.
return Take!(R, true)(range, n);
}
else
{
return Take!(R, false)(range, n);
}
}
///
@nogc nothrow pure @safe unittest
{
static struct InfiniteRange
{
private size_t front_ = 1;
enum bool empty = false;
@property size_t front() @nogc nothrow pure @safe
{
return this.front_;
}
@property void front(size_t i) @nogc nothrow pure @safe
{
this.front_ = i;
}
void popFront() @nogc nothrow pure @safe
{
++this.front_;
}
size_t opIndex(size_t i) @nogc nothrow pure @safe
{
return this.front_ + i;
}
void opIndexAssign(size_t value, size_t i) @nogc nothrow pure @safe
{
this.front = i + value;
}
InfiniteRange save() @nogc nothrow pure @safe
{
return this;
}
}
auto t = InfiniteRange().take(3);
assert(t.length == 3);
assert(t.front == 1);
assert(t.back == 3);
t.popFront();
assert(t.front == 2);
assert(t.back == 3);
t.popBack();
assert(t.front == 2);
assert(t.back == 2);
t.popFront();
assert(t.empty);
}
/**
* Takes exactly $(D_PARAM n) elements from $(D_PARAM range).
*
* $(D_PARAM range) must have at least $(D_PARAM n) elements.
*
* $(D_PSYMBOL takeExactly) is particulary useful with infinite ranges. You can
` take $(B n) elements from such range and pass the result to an algorithm
* which expects a finit range.
*
* Params:
* R = Type of the adapted range.
* range = The range to take the elements from.
* n = The number of elements to take.
*
* Returns: A range containing $(D_PARAM n) first elements of $(D_PARAM range).
*
* See_Also: $(D_PSYMBOL take).
*/
auto takeExactly(R)(R range, size_t n)
if (isInputRange!R)
{
static if (hasSlicing!R)
{
return range[0 .. n];
}
// Special case: takeExactly(take(range, ...), n) is takeExactly(range, n)
else static if (is(Range == Take!(RRange, exact), RRange, bool exact))
{
assert(n <= range.length_);
return Take!(RRange, true)(range.source, n);
}
else
{
return Take!(R, true)(range, n);
}
}
///
@nogc nothrow pure @safe unittest
{
static struct InfiniteRange
{
private size_t front_ = 1;
enum bool empty = false;
@property size_t front() @nogc nothrow pure @safe
{
return this.front_;
}
@property void front(size_t i) @nogc nothrow pure @safe
{
this.front_ = i;
}
void popFront() @nogc nothrow pure @safe
{
++this.front_;
}
size_t opIndex(size_t i) @nogc nothrow pure @safe
{
return this.front_ + i;
}
void opIndexAssign(size_t value, size_t i) @nogc nothrow pure @safe
{
this.front = i + value;
}
InfiniteRange save() @nogc nothrow pure @safe
{
return this;
}
}
auto t = InfiniteRange().takeExactly(3);
assert(t.length == 3);
assert(t.front == 1);
assert(t.back == 3);
t.popFront();
assert(t.front == 2);
assert(t.back == 3);
t.popBack();
assert(t.front == 2);
assert(t.back == 2);
t.popFront();
assert(t.empty);
}
// Reverse-access-order range returned by `retro`.
private struct Retro(Range)
{
Range source;
@disable this();
private this(Range source) @safe
{
this.source = source;
}
mixin(`Retro save() ` ~ (hasConstSave!Range ? `const ` : ``) ~
`{
return Retro(source.save());
}`);
mixin(`@property auto ref front() ` ~ (hasInoutBack!Range ? `inout ` : ``) ~
`in (!empty)
{
return this.source.back;
}`);
void popFront()
in (!empty)
{
this.source.popBack();
}
mixin(`@property auto ref back() ` ~ (hasInoutFront!Range ? `inout ` : ``) ~
`in (!empty)
{
return this.source.front;
}`);
void popBack()
in (!empty)
{
this.source.popFront();
}
mixin(`@property bool empty() ` ~ (hasConstEmpty!Range ? `const ` : ``) ~
`{
return this.source.empty;
}`);
static if (hasLength!Range)
{
mixin(`@property size_t length() ` ~ (hasConstLength!Range ? `const ` : ``) ~
`{
return this.source.length;
}`);
}
static if (isRandomAccessRange!Range && hasLength!Range)
{
mixin(`auto ref opIndex(size_t i) ` ~ (hasInoutIndex!Range ? `inout ` : ``) ~
`in (i < length)
{
return this.source[$ - ++i];
}`);
}
static if (hasLength!Range && hasSlicing!Range)
{
alias opDollar = length;
mixin(`auto opSlice(size_t i, size_t j) ` ~ (hasConstSlice!Range ? `const ` : ``) ~
`in (i <= j)
in (j <= length)
{
return typeof(this)(this.source[$-j .. $-i]);
}`);
}
static if (hasAssignableElements!Range)
{
@property void front(ref ElementType!Range value)
in (!empty)
{
this.source.back = value;
}
@property void front(ElementType!Range value)
in (!empty)
{
this.source.back = move(value);
}
@property void back(ref ElementType!Range value)
in (!empty)
{
this.source.front = value;
}
@property void back(ElementType!Range value)
in (!empty)
{
this.source.front = move(value);
}
static if (isRandomAccessRange!Range && hasLength!Range)
{
void opIndexAssign(ref ElementType!Range value, size_t i)
in (i < length)
{
this.source[$ - ++i] = value;
}
void opIndexAssign(ElementType!Range value, size_t i)
in (i < length)
{
this.source[$ - ++i] = move(value);
}
}
}
version (unittest) static assert(isBidirectionalRange!Retro);
}
/**
* Iterates a bidirectional range backwards.
*
* If $(D_PARAM Range) is a random-access range as well, the resulting range
* is a random-access range too.
*
* Params:
* Range = Bidirectional range type.
* range = Bidirectional range.
*
* Returns: Bidirectional range with the elements order reversed.
*/
auto retro(Range)(Range range)
if (isBidirectionalRange!Range)
{
// Special case: retro(retro(range)) is range
static if (is(Range == Retro!RRange, RRange))
return range.source;
else
return Retro!Range(range);
}
///
@nogc nothrow pure @safe unittest
{
import tanya.algorithm.comparison : equal;
const int[3] given = [1, 2, 3];
const int[3] expected = [3, 2, 1];
auto actual = retro(given[]);
assert(actual.length == expected.length);
assert(!actual.empty);
assert(equal(actual, expected[]));
}
private struct SingletonByValue(E)
{
private Option!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 == Option!(Unqual!E)) || is(Unqual!U == Option!(const E)))
{
if (!element.isNothing)
{
this.element = element.get;
}
}
@property ref inout(E) front() inout
in (!empty)
{
return this.element.get;
}
alias back = front;
void popFront()
in (!empty)
{
this.element.reset();
}
alias popBack = popFront;
@property bool empty() const
{
return this.element.isNothing;
}
@property size_t length() const
{
return !this.element.isNothing;
}
auto save()
{
return SingletonByValue!E(this.element);
}
auto save() const
{
return SingletonByValue!(const E)(this.element);
}
ref inout(E) opIndex(size_t i) inout
in (!empty)
in (i == 0)
{
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 (!empty)
{
return *this.element;
}
alias back = front;
void popFront()
in (!empty)
{
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);
}
auto save() const return
{
return SingletonByRef!(const E)(*this.element);
}
ref inout(E) opIndex(size_t i) inout return
in (!empty)
in (i == 0)
{
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 foldl) takes a function, an input 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 foldl) accumulates from left to right.
*
* Params:
* F = Callable accepting the accumulator and a range element.
*/
template foldl(F...)
if (F.length == 1)
{
/**
* Params:
* R = Input range type.
* T = Type of the accumulated value.
* range = Input range.
* init = Initial value.
*
* Returns: Accumulated value.
*/
auto foldl(R, T)(R range, auto ref T init)
if (isInputRange!R && !isInfinite!R)
{
if (range.empty)
{
return init;
}
else
{
auto acc = F[0](init, getAndPopFront(range));
return foldl(range, acc);
}
}
}
///
@nogc nothrow pure @safe unittest
{
int[3] range = [1, 2, 3];
const actual = foldl!((acc, x) => acc + x)(range[], 0);
assert(actual == 6);
}
/**
* Accumulates all elements of a range using a function.
*
* $(D_PSYMBOL foldr) takes a function, an input 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)(R range, 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

@ -5,7 +5,7 @@
/**
* Algorithms that modify its arguments.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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,262 +14,293 @@
*/
module tanya.algorithm.mutation;
import tanya.memory.op;
static import tanya.memory.lifetime;
static import tanya.memory.op;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range;
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
{
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]);
}
}
}
deprecated("Use tanya.memory.lifetime.swap instead")
alias swap = tanya.memory.lifetime.swap;
deprecated("Use tanya.memory.lifetime.moveEmplace instead")
alias moveEmplace = tanya.memory.lifetime.moveEmplace;
deprecated("Use tanya.memory.lifetime.move instead")
alias move = tanya.memory.lifetime.move;
/**
* 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.
* Copies the $(D_PARAM source) range into the $(D_PARAM target) range.
*
* Params:
* T = Object type.
* source = Source object.
* target = Target object.
* Source = Input range type.
* Target = Output range type.
* source = Source input range.
* target = Target output range.
*
* See_Also: $(D_PSYMBOL move),
* $(D_PSYMBOL hasElaborateCopyConstructor),
* $(D_PSYMBOL hasElaborateDestructor).
* Returns: $(D_PARAM target) range, whose front element is the one past the
* last element copied.
*
* Precondition: `&source !is &target`.
* Precondition: $(D_PARAM target) should be large enough to accept all
* $(D_PARAM source) elements.
*/
void moveEmplace(T)(ref T source, ref T target) @system
Target copy(Source, Target)(Source source, Target target)
if (isInputRange!Source && isOutputRange!(Target, ElementType!Source))
in
{
assert(&source !is &target, "Source and target must be different");
}
body
{
static if (is(T == struct) || isStaticArray!T)
static if (hasLength!Source && hasLength!Target)
{
copy((&source)[0 .. 1], (&target)[0 .. 1]);
static if (hasElaborateCopyConstructor!T || hasElaborateDestructor!T)
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)())
{
if (typeid(T).initializer().ptr is null)
{
deinitialize!true(source);
}
else
{
deinitialize!false(source);
}
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 tanya.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 tanya.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
{
target = source;
fill(range, value);
}
}
///
@nogc nothrow pure @system unittest
@nogc nothrow pure @safe unittest
{
static struct S
{
int member = 5;
import tanya.algorithm.comparison : equal;
this(this) @nogc nothrow pure @safe
{
assert(false);
}
}
S source, target = void;
moveEmplace(source, target);
assert(target.member == 5);
int[6] actual = void;
const int[6] expected = [1, 1, 1, 1, 1, 1];
int x1 = 5, x2;
moveEmplace(x1, x2);
assert(x2 == 5);
}
// 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);
uninitializedFill(actual[], 1);
assert(equal(actual[], expected[]));
}
/**
* 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.
* Initializes all elements of the $(D_PARAM range) assuming that they are
* uninitialized.
*
* Params:
* T = Object type.
* source = Source object.
* target = Target object.
*
* See_Also: $(D_PSYMBOL moveEmplace).
* Range = Input range type
* range = Input range.
*/
void move(T)(ref T source, ref T target)
void initializeAll(Range)(Range range) @trusted
if (isInputRange!Range && hasLvalueElements!Range)
{
if ((() @trusted => &source is &target)())
{
return;
}
static if (hasElaborateDestructor!T)
{
target.__xdtor();
}
(() @trusted => moveEmplace(source, target))();
}
import tanya.memory.op : copy, fill;
alias T = ElementType!Range;
/// ditto
T move(T)(ref T source) @trusted
{
T target = void;
moveEmplace(source, target);
return target;
static if (__VERSION__ >= 2083
&& 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
{
static struct S
{
int member = 5;
import tanya.algorithm.comparison : equal;
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[2] actual = void;
const int[2] expected = [0, 0];
int x1 = 5, x2;
move(x1, x2);
assert(x2 == 5);
assert(move(x2) == 5);
}
// Moves if source is target.
@nogc nothrow pure @safe unittest
{
int x = 5;
move(x, x);
assert(x == 5);
initializeAll(actual[]);
assert(equal(actual[], expected[]));
}
/**
* Exchanges the values of $(D_PARAM a) and $(D_PARAM b).
* Destroys all elements in the $(D_PARAM range).
*
* $(D_PSYMBOL swap) moves the contents of $(D_PARAM a) and $(D_PARAM b)
* without calling its postblits or destructors.
* 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:
* a = The first object.
* a = The second object.
* Range = Input range type.
* range = Input range.
*/
void swap(T)(ref T a, ref T b) @trusted
void destroyAll(Range)(Range range)
if (isInputRange!Range && hasLvalueElements!Range)
{
T tmp = void;
moveEmplace(a, tmp);
moveEmplace(b, a);
moveEmplace(tmp, b);
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);
}
/**
* Rotates the elements of a union of two ranges.
*
* Performs a left rotation on the given ranges, as if it would be a signle
* range, so that [`front.front`, `back.front`$(RPAREN) is a valid range, that
* is $(D_PARAM back) would continue $(D_PARAM front).
*
* The elements are moved so, that the first element of $(D_PARAM back) becomes
* the first element of $(D_PARAM front) without changing the relative order of
* their elements.
*
* Params:
* Range = Range type.
* front = Left half.
* back = Right half.
*/
void rotate(Range)(Range front, Range back)
if (isForwardRange!Range && hasSwappableElements!Range)
{
auto next = back.save();
while (!front.empty && !next.empty && !sameHead(front, next))
{
tanya.memory.lifetime.swap(front.front, next.front);
front.popFront();
next.popFront();
if (next.empty)
{
next = back.save();
}
else if (front.empty)
{
front = back.save();
back = next.save();
}
}
}
///
@nogc nothrow pure @safe unittest
{
int a = 3, b = 5;
swap(a, b);
assert(a == 5);
assert(b == 3);
import tanya.algorithm.comparison : equal;
const int[7] expected = [1, 2, 3, 4, 5, 6, 7];
int[7] actual = [5, 6, 3, 4, 1, 2, 7];
rotate(actual[0 .. 2], actual[4 .. 6]);
assert(equal(actual[], expected[]));
}

View File

@ -5,7 +5,7 @@
/**
* Collection of generic algorithms.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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,4 +14,7 @@
*/
module tanya.algorithm;
public import tanya.algorithm.comparison;
public import tanya.algorithm.iteration;
public import tanya.algorithm.mutation;
public import tanya.algorithm.searching;

View File

@ -0,0 +1,53 @@
/* 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/. */
/**
* Searching algorithms.
*
* Copyright: Eugene Wissner 2018-2019.
* 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/searching.d,
* tanya/algorithm/searching.d)
*/
module tanya.algorithm.searching;
import tanya.range;
/**
* Counts the elements in an input range.
*
* If $(D_PARAM R) has length, $(D_PSYMBOL count) returns it, otherwise it
* iterates over the range and counts the elements.
*
* Params:
* R = Input range type.
* range = Input range.
*
* Returns: $(D_PARAM range) length.
*/
size_t count(R)(R range)
if (isInputRange!R)
{
static if (hasLength!R)
{
return range.length;
}
else
{
size_t counter;
for (; !range.empty; range.popFront(), ++counter)
{
}
return counter;
}
}
///
@nogc nothrow pure @safe unittest
{
int[3] array;
assert(count(array) == 3);
}

View File

@ -1,188 +1,187 @@
/* 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):
import core.stdc.errno;
public import core.sys.linux.epoll;
import core.sys.posix.unistd;
import core.time;
import std.algorithm.comparison;
import tanya.async.event.selector;
import tanya.async.loop;
import tanya.async.protocol;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.array;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
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");
}
}
/* 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-2019.
* 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):
import core.stdc.errno;
public import core.sys.linux.epoll;
import core.sys.posix.unistd;
import core.time;
import tanya.algorithm.comparison;
import tanya.async.event.selector;
import tanya.async.loop;
import tanya.async.protocol;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.array;
import tanya.memory.allocator;
import tanya.network.socket;
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);
}
/**
* 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.insertBack(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

@ -5,7 +5,7 @@
/**
* Event loop implementation for Windows.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2019.
* 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)
@ -26,8 +26,7 @@ import tanya.async.protocol;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.buffer;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.memory.allocator;
import tanya.network.socket;
import tanya.sys.windows.winbase;
@ -57,8 +56,8 @@ final class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
this(OverlappedConnectedSocket socket) @nogc
{
super(socket);
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
input = WriteBuffer!ubyte(8192, MmapPool.instance);
output = ReadBuffer!ubyte(8192, 1024);
input = WriteBuffer!ubyte(8192);
active = true;
}
@ -68,11 +67,7 @@ final class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
* Postcondition: $(D_INLINECODE socket !is null)
*/
override @property OverlappedConnectedSocket socket() pure nothrow @safe @nogc
out (socket)
{
assert(socket !is null);
}
body
out (socket; socket !is null)
{
return cast(OverlappedConnectedSocket) socket_;
}
@ -117,8 +112,7 @@ final class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
/**
* Switches the protocol.
*
* The protocol is deallocated by the event loop, it should currently be
* allocated with $(D_PSYMBOL MmapPool).
* The protocol is deallocated by the event loop.
*
* Params:
* protocol = Application protocol.
@ -126,11 +120,7 @@ final class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
* Precondition: $(D_INLINECODE protocol !is null)
*/
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
in
{
assert(protocol !is null);
}
body
in (protocol !is null)
{
protocol_ = protocol;
}
@ -150,20 +140,20 @@ final class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
SocketState overlapped;
try
{
overlapped = MmapPool.instance.make!SocketState;
overlapped = defaultAllocator.make!SocketState;
socket.beginSend(input[], overlapped);
}
catch (SocketException e)
{
MmapPool.instance.dispose(overlapped);
MmapPool.instance.dispose(e);
defaultAllocator.dispose(overlapped);
defaultAllocator.dispose(e);
}
}
}
else
{
protocol.disconnected(exception);
MmapPool.instance.dispose(protocol_);
defaultAllocator.dispose(protocol_);
defaultAllocator.dispose(exception);
active = false;
}
@ -221,12 +211,12 @@ final class IOCPLoop : Loop
try
{
overlapped = MmapPool.instance.make!SocketState;
overlapped = defaultAllocator.make!SocketState;
socket.beginAccept(overlapped);
}
catch (SocketException e)
{
MmapPool.instance.dispose(overlapped);
defaultAllocator.dispose(overlapped);
defaultAllocator.dispose(e);
return false;
}
@ -250,12 +240,12 @@ final class IOCPLoop : Loop
{
try
{
overlapped = MmapPool.instance.make!SocketState;
overlapped = defaultAllocator.make!SocketState;
transport.socket.beginReceive(transport.output[], overlapped);
}
catch (SocketException e)
{
MmapPool.instance.dispose(overlapped);
defaultAllocator.dispose(overlapped);
defaultAllocator.dispose(e);
return false;
}
@ -266,16 +256,12 @@ final class IOCPLoop : Loop
private void kill(StreamTransport transport,
SocketException exception = null) @nogc
in
{
assert(transport !is null);
}
body
in (transport !is null)
{
transport.socket.shutdown();
defaultAllocator.dispose(transport.socket);
transport.exception = exception;
pendings.enqueue(transport);
pendings.insertBack(transport);
}
/**
@ -298,11 +284,12 @@ final class IOCPLoop : Loop
return; // Timeout
}
auto overlapped = (cast(SocketState) ((cast(void*) overlap) - 8));
enum size_t offset = size_t.sizeof * 2;
auto overlapped = cast(SocketState) ((cast(void*) overlap) - offset);
assert(overlapped !is null);
scope (failure)
{
MmapPool.instance.dispose(overlapped);
defaultAllocator.dispose(overlapped);
}
switch (overlapped.event)
@ -315,13 +302,15 @@ final class IOCPLoop : Loop
assert(listener !is null);
auto socket = listener.endAccept(overlapped);
auto transport = MmapPool.instance.make!StreamTransport(socket);
auto transport = defaultAllocator.make!StreamTransport(socket);
connection.incoming.enqueue(transport);
connection.incoming.insertBack(transport);
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
reify(transport,
EventMask(Event.none),
EventMask(Event.read | Event.write));
pendings.enqueue(connection);
pendings.insertBack(connection);
listener.beginAccept(overlapped);
break;
case OverlappedSocketEvent.read:
@ -330,8 +319,8 @@ final class IOCPLoop : Loop
if (!transport.active)
{
MmapPool.instance.dispose(transport);
MmapPool.instance.dispose(overlapped);
defaultAllocator.dispose(transport);
defaultAllocator.dispose(overlapped);
return;
}
@ -361,7 +350,7 @@ final class IOCPLoop : Loop
{
transport.socket.beginReceive(transport.output[], overlapped);
}
pendings.enqueue(transport);
pendings.insertBack(transport);
}
break;
case OverlappedSocketEvent.write:

View File

@ -5,7 +5,7 @@
/*
* Event loop implementation for *BSD.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2019.
* 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)
@ -52,14 +52,13 @@ import core.stdc.errno;
import core.sys.posix.time; // timespec
import core.sys.posix.unistd;
import core.time;
import std.algorithm.comparison;
import tanya.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.memory.allocator;
import tanya.network.socket;
void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc
@ -85,12 +84,12 @@ enum : short
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 */
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
@ -146,8 +145,8 @@ final class KqueueLoop : SelectorLoop
throw make!BadLoopException(defaultAllocator,
"kqueue initialization failed");
}
events = Array!kevent_t(64, MmapPool.instance);
changes = Array!kevent_t(64, MmapPool.instance);
events = Array!kevent_t(64);
changes = Array!kevent_t(64);
}
/**
@ -169,7 +168,7 @@ final class KqueueLoop : SelectorLoop
filter,
flags,
0U,
0L,
0,
null);
++changeCount;
}
@ -218,7 +217,7 @@ final class KqueueLoop : SelectorLoop
timespec ts;
blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec);
if (changeCount > maxEvents)
if (changeCount > maxEvents)
{
events.length = changes.length;
}
@ -280,7 +279,7 @@ final class KqueueLoop : SelectorLoop
}
else if (transport.output.length)
{
pendings.enqueue(transport);
pendings.insertBack(transport);
}
}
else if (events[i].filter == EVFILT_WRITE)

View File

@ -5,7 +5,7 @@
/*
* This module contains base implementations for reactor event loops.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2019.
* 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)
@ -25,8 +25,7 @@ import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.array;
import tanya.container.buffer;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.memory.allocator;
import tanya.network.socket;
/**
@ -61,12 +60,12 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
{
assert(loop !is null);
}
body
do
{
super(socket);
this.loop = loop;
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
input = WriteBuffer!ubyte(8192, MmapPool.instance);
output = ReadBuffer!ubyte(8192, 1024);
input = WriteBuffer!ubyte(8192);
active = true;
}
@ -76,22 +75,14 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
* Postcondition: $(D_INLINECODE socket !is null)
*/
override @property ConnectedSocket socket() pure nothrow @safe @nogc
out (socket)
{
assert(socket !is null);
}
body
out (socket; socket !is null)
{
return cast(ConnectedSocket) socket_;
}
private @property void socket(ConnectedSocket socket)
pure nothrow @safe @nogc
in
{
assert(socket !is null);
}
body
in (socket !is null)
{
socket_ = socket;
}
@ -107,8 +98,7 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
/**
* Switches the protocol.
*
* The protocol is deallocated by the event loop, it should currently be
* allocated with $(D_PSYMBOL MmapPool).
* The protocol is deallocated by the event loop.
*
* Params:
* protocol = Application protocol.
@ -116,11 +106,7 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
* Precondition: $(D_INLINECODE protocol !is null)
*/
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
in
{
assert(protocol !is null);
}
body
in (protocol !is null)
{
protocol_ = protocol;
}
@ -142,7 +128,7 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
{
closing = true;
loop.reify(this,
EventMask(Event.read, Event.write),
EventMask(Event.read | Event.write),
EventMask(Event.write));
}
@ -163,7 +149,7 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
else
{
protocol.disconnected(exception);
MmapPool.instance.dispose(protocol_);
defaultAllocator.dispose(protocol_);
defaultAllocator.dispose(exception);
active = false;
}
@ -220,18 +206,18 @@ abstract class SelectorLoop : Loop
this() @nogc
{
super();
connections = Array!SocketWatcher(maxEvents, MmapPool.instance);
this.connections = Array!SocketWatcher(maxEvents);
}
~this() @nogc
{
foreach (ref connection; connections)
foreach (ref connection; this.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);
defaultAllocator.dispose(connection);
}
}
}
@ -259,16 +245,12 @@ abstract class SelectorLoop : Loop
*/
protected void kill(StreamTransport transport,
SocketException exception = null) @nogc
in
{
assert(transport !is null);
}
body
in (transport !is null)
{
transport.socket.shutdown();
defaultAllocator.dispose(transport.socket);
transport.exception = exception;
pendings.enqueue(transport);
pendings.insertBack(transport);
}
/**
@ -285,11 +267,7 @@ abstract class SelectorLoop : Loop
*/
protected bool feed(StreamTransport transport,
SocketException exception = null) @nogc
in
{
assert(transport !is null);
}
body
in (transport !is null)
{
while (transport.input.length && transport.writeReady)
{
@ -352,11 +330,7 @@ abstract class SelectorLoop : Loop
* connection = Connection watcher ready to accept.
*/
package void acceptConnections(ConnectionWatcher connection) @nogc
in
{
assert(connection !is null);
}
body
in (connection !is null)
{
while (true)
{
@ -387,7 +361,7 @@ abstract class SelectorLoop : Loop
}
if (transport is null)
{
transport = MmapPool.instance.make!StreamTransport(this, client);
transport = defaultAllocator.make!StreamTransport(this, client);
connections[client.handle] = transport;
}
else
@ -395,13 +369,15 @@ abstract class SelectorLoop : Loop
transport.socket = client;
}
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
connection.incoming.enqueue(transport);
reify(transport,
EventMask(Event.none),
EventMask(Event.read | Event.write));
connection.incoming.insertBack(transport);
}
if (!connection.incoming.empty)
{
pendings.enqueue(connection);
pendings.insertBack(connection);
}
}
}

View File

@ -7,7 +7,7 @@
*
* Note: Available only on Windows.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2019.
* 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)
@ -53,4 +53,4 @@ class State
/// For keeping events or event masks.
int event;
}
}

View File

@ -13,54 +13,56 @@
*
* class EchoProtocol : TransmissionControlProtocol
* {
* private DuplexTransport transport;
* private DuplexTransport transport;
*
* void received(in ubyte[] data) @nogc
* {
* transport.write(data);
* }
* void received(in ubyte[] data) @nogc
* {
* ubyte[512] buffer;
* buffer[0 .. data.length] = data;
* transport.write(buffer[]);
* }
*
* void connected(DuplexTransport transport) @nogc
* {
* this.transport = transport;
* }
* void connected(DuplexTransport transport) @nogc
* {
* this.transport = transport;
* }
*
* void disconnected(SocketException e) @nogc
* {
* }
* void disconnected(SocketException e) @nogc
* {
* }
* }
*
* void main()
* {
* auto address = defaultAllocator.make!InternetAddress("127.0.0.1", cast(ushort) 8192);
* 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;
* }
* 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);
* sock.bind(address);
* sock.listen(5);
*
* auto io = defaultAllocator.make!ConnectionWatcher(sock);
* io.setProtocol!EchoProtocol;
* auto io = defaultAllocator.make!ConnectionWatcher(sock);
* io.setProtocol!EchoProtocol;
*
* defaultLoop.start(io);
* defaultLoop.run();
* defaultLoop.start(io);
* defaultLoop.run();
*
* sock.shutdown();
* defaultAllocator.dispose(io);
* defaultAllocator.dispose(sock);
* defaultAllocator.dispose(address);
* sock.shutdown();
* defaultAllocator.dispose(io);
* defaultAllocator.dispose(sock);
* defaultAllocator.dispose(address);
* }
* ---
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2019.
* 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)
@ -70,15 +72,12 @@
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.bitmanip;
import tanya.container.buffer;
import tanya.container.queue;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.container.list;
import tanya.memory.allocator;
import tanya.network.socket;
version (DisableBackends)
@ -117,30 +116,6 @@ 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.
@ -161,10 +136,10 @@ alias EventMask = BitFlags!Event;
*/
abstract class Loop
{
private bool done = true;
protected bool done = true;
/// Pending watchers.
protected Queue!Watcher pendings;
protected DList!Watcher pendings;
/**
* Returns: Maximal event count can be got at a time
@ -176,20 +151,11 @@ abstract class Loop
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);
}
/**
@ -197,9 +163,9 @@ abstract class Loop
*/
~this() @nogc
{
foreach (w; pendings)
for (; !this.pendings.empty; this.pendings.removeFront())
{
MmapPool.instance.dispose(w);
defaultAllocator.dispose(this.pendings.front);
}
}
@ -214,9 +180,9 @@ abstract class Loop
poll();
// Invoke pendings
foreach (ref w; this.pendings)
for (; !this.pendings.empty; this.pendings.removeFront())
{
w.invoke();
this.pendings.front.invoke();
}
}
while (!this.done);
@ -230,31 +196,6 @@ abstract class Loop
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.
*
@ -321,27 +262,12 @@ abstract class Loop
* $(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
in (blockTime <= 1.dur!"hours", "Too long to wait.")
in (!blockTime.isNegative)
{
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.
*/
@ -384,16 +310,16 @@ class BadLoopException : Exception
}
version (Epoll)
{
defaultLoop_ = MmapPool.instance.make!EpollLoop;
defaultLoop_ = defaultAllocator.make!EpollLoop;
}
else version (IOCP)
{
defaultLoop_ = MmapPool.instance.make!IOCPLoop;
defaultLoop_ = defaultAllocator.make!IOCPLoop;
}
else version (Kqueue)
{
import tanya.async.event.kqueue;
defaultLoop_ = MmapPool.instance.make!KqueueLoop;
defaultLoop_ = defaultAllocator.make!KqueueLoop;
}
return defaultLoop_;
}
@ -410,26 +336,9 @@ class BadLoopException : Exception
* loop = The event loop.
*/
@property void defaultLoop(Loop loop) @nogc
in
{
assert(loop !is null);
}
body
in (loop !is null)
{
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

@ -5,7 +5,7 @@
/**
* This package provides asynchronous capabilities.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2019.
* 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)

View File

@ -9,7 +9,7 @@
* When an event from the network arrives, a protocol method gets
* called and can respond to the event.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2019.
* 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)

View File

@ -6,7 +6,7 @@
* This module contains transports which are responsible for data dilvery
* between two parties of an asynchronous communication.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2019.
* 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)
@ -57,16 +57,12 @@ interface DuplexTransport : ReadTransport, WriteTransport
* Postcondition: $(D_INLINECODE protocol !is null)
*/
@property Protocol protocol() pure nothrow @safe @nogc
out (protocol)
{
assert(protocol !is null);
}
out (protocol; protocol !is null);
/**
* Switches the protocol.
*
* The protocol is deallocated by the event loop, it should currently be
* allocated with $(D_PSYMBOL MmapPool).
* The protocol is deallocated by the event loop.
*
* Params:
* protocol = Application protocol.
@ -74,10 +70,7 @@ interface DuplexTransport : ReadTransport, WriteTransport
* Precondition: $(D_INLINECODE protocol !is null)
*/
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
in
{
assert(protocol !is null);
}
in (protocol !is null);
/**

View File

@ -5,7 +5,7 @@
/**
* Watchers register user's interest in some event.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2019.
* 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,15 +14,12 @@
*/
module tanya.async.watcher;
import std.exception;
import std.functional;
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.container.list;
import tanya.memory.allocator;
import tanya.network.socket;
/**
@ -40,19 +37,6 @@ abstract class Watcher
void invoke() @nogc;
}
version (unittest)
{
final class DummyWatcher : Watcher
{
bool invoked;
override void invoke() @nogc
{
this.invoked = true;
}
}
}
/**
* Socket watcher.
*/
@ -68,11 +52,7 @@ abstract class SocketWatcher : Watcher
* Precondition: $(D_INLINECODE socket !is null)
*/
this(Socket socket) pure nothrow @safe @nogc
in
{
assert(socket !is null);
}
body
in (socket !is null)
{
socket_ = socket;
}
@ -92,7 +72,7 @@ abstract class SocketWatcher : Watcher
class ConnectionWatcher : SocketWatcher
{
/// Incoming connection queue.
Queue!DuplexTransport incoming;
DList!DuplexTransport incoming;
private Protocol delegate() @nogc protocolFactory;
@ -103,7 +83,6 @@ class ConnectionWatcher : SocketWatcher
this(Socket socket) @nogc
{
super(socket);
incoming = Queue!DuplexTransport(MmapPool.instance);
}
/**
@ -112,23 +91,19 @@ class ConnectionWatcher : SocketWatcher
*/
void setProtocol(P : Protocol)() @nogc
{
this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P;
this.protocolFactory = () @nogc => cast(Protocol) defaultAllocator.make!P;
}
/**
* Invokes new connection callback.
*/
override void invoke() @nogc
in
in (protocolFactory !is null, "Protocol isn't set.")
{
assert(protocolFactory !is null, "Protocol isn't set.");
}
body
{
foreach (transport; incoming)
for (; !this.incoming.empty; this.incoming.removeFront())
{
transport.protocol = protocolFactory();
transport.protocol.connected(transport);
this.incoming.front.protocol = protocolFactory();
this.incoming.front.protocol.connected(this.incoming.front);
}
}
}

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-2019.
* 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-2019.
* 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,15 @@
*/
module tanya.container.buffer;
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
in (start < end)
{
auto numberRead = end - start;
for (ubyte i; i < numberRead; ++i)
@ -52,7 +47,7 @@ version (unittest)
* T = Buffer type.
*/
struct ReadBuffer(T = ubyte)
if (isScalarType!T)
if (isScalarType!T)
{
/// Internal buffer.
private T[] buffer_;
@ -67,17 +62,14 @@ 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);
}
invariant (this.length_ <= this.buffer_.length);
invariant (this.blockSize > 0);
invariant (this.minAvailable > 0);
/**
* Creates a new read buffer.
@ -90,23 +82,19 @@ 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
this(shared Allocator allocator)
in
{
assert(allocator_ is null);
}
body
in (allocator_ is null)
{
allocator_ = allocator;
}
@ -116,11 +104,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 +120,7 @@ struct ReadBuffer(T = ubyte)
*/
@property size_t capacity() const
{
return buffer_.length;
return this.buffer_.length;
}
/**
@ -140,7 +128,7 @@ struct ReadBuffer(T = ubyte)
*/
@property size_t length() const
{
return length_ - start;
return this.length_ - start;
}
/// ditto
@ -153,7 +141,7 @@ struct ReadBuffer(T = ubyte)
*/
void clear()
{
start = length_ = ring;
start = this.length_ = ring;
}
/**
@ -165,7 +153,7 @@ struct ReadBuffer(T = ubyte)
}
///
unittest
@nogc nothrow pure @system unittest
{
ReadBuffer!ubyte b;
size_t numberRead;
@ -173,7 +161,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 +176,23 @@ struct ReadBuffer(T = ubyte)
*
* Returns: $(D_KEYWORD this).
*/
ref ReadBuffer opOpAssign(string op)(in size_t length)
if (op == "+")
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 +202,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 +223,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 +239,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 +282,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.
@ -311,7 +295,7 @@ private unittest
* T = Buffer type.
*/
struct WriteBuffer(T = ubyte)
if (isScalarType!T)
if (isScalarType!T)
{
/// Internal buffer.
private T[] buffer_;
@ -323,17 +307,14 @@ 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);
}
invariant (this.blockSize > 0);
// Position can refer to an element outside the buffer if the buffer is full.
invariant (this.position <= this.buffer_.length);
/**
* Params:
@ -343,18 +324,14 @@ struct WriteBuffer(T = ubyte)
*
* Precondition: $(D_INLINECODE size > 0 && allocator !is null)
*/
this(in size_t size, shared Allocator allocator = defaultAllocator) @trusted
in
this(size_t size, shared Allocator allocator = defaultAllocator) @trusted
in (size > 0)
in (allocator !is null)
{
assert(size > 0);
assert(allocator !is null);
}
body
{
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 +341,7 @@ struct WriteBuffer(T = ubyte)
*/
~this()
{
allocator.deallocate(buffer_);
allocator.deallocate(this.buffer_);
}
/**
@ -372,7 +349,7 @@ struct WriteBuffer(T = ubyte)
*/
@property size_t capacity() const
{
return buffer_.length;
return this.buffer_.length;
}
/**
@ -385,13 +362,13 @@ 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;
}
}
@ -399,7 +376,7 @@ struct WriteBuffer(T = ubyte)
alias opDollar = length;
///
unittest
@nogc nothrow pure @system unittest
{
auto b = WriteBuffer!ubyte(4);
ubyte[3] buf = [48, 23, 255];
@ -434,61 +411,62 @@ struct WriteBuffer(T = ubyte)
* Params:
* buffer = Buffer chunk got with $(D_PSYMBOL opIndex).
*/
ref WriteBuffer opOpAssign(string op)(in T[] buffer)
if (op == "~")
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 +476,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 +485,9 @@ struct WriteBuffer(T = ubyte)
*
* Returns: $(D_KEYWORD this).
*/
ref WriteBuffer opOpAssign(string op)(in size_t length)
if (op == "+")
in
{
assert(length <= this.length);
}
body
ref WriteBuffer opOpAssign(string op)(size_t length)
if (op == "+")
in (length <= this.length)
{
auto afterRing = ring + 1;
auto oldStart = start;
@ -558,42 +496,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 +545,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 +571,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 +622,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-2019.
* 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 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,267 @@ 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_;
}
bool opEquals(ref T content)
{
if (this.status == BucketStatus.used && this.content == content)
static if (is(V == void))
{
return true;
return this.key_;
}
return false;
}
bool opEquals(ref const T content) const
{
if (this.status == BucketStatus.used && this.content == content)
else
{
return true;
return this.kv.key;
}
return false;
}
bool opEquals(ref typeof(this) that)
void moveKey(ref K key)
{
return this.content == that.content && this.status == that.status;
move(key, this.key());
this.status = BucketStatus.used;
}
bool opEquals(ref typeof(this) that) const
bool opEquals(T)(ref const T key) const
{
return this.content == that.content && this.status == that.status;
return this.status == BucketStatus.used && this.key == key;
}
bool opEquals(ref const(typeof(this)) that) const
{
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 (allocator !is null)
{
this.array = Buckets(allocator);
}
this(T)(ref T data, shared Allocator allocator)
if (is(Unqual!T == HashArray))
in (allocator !is null)
{
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 (allocator !is null)
{
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 (n < primes.length)
{
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;
}
}

View File

@ -0,0 +1,987 @@
/* 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/. */
/**
* Hash table.
*
* Copyright: Eugene Wissner 2018-2019.
* 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/hashtable.d,
* tanya/container/hashtable.d)
*/
module tanya.container.hashtable;
import tanya.algorithm.iteration;
import tanya.algorithm.mutation;
import tanya.container.array;
import tanya.container.entry;
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 whose element type is a tuple of a key and the
* respective value.
*
* Params:
* T = Type of the internal hash storage.
*/
struct Range(T)
{
private alias KV = CopyConstness!(T, T.Bucket.KV);
static if (isMutable!T)
{
private alias DataRange = T.array.Range;
}
else
{
private alias DataRange = T.array.ConstRange;
}
private DataRange dataRange;
@disable this();
private this(DataRange dataRange)
{
while (!dataRange.empty && dataRange.front.status != BucketStatus.used)
{
dataRange.popFront();
}
while (!dataRange.empty && dataRange.back.status != BucketStatus.used)
{
dataRange.popBack();
}
this.dataRange = dataRange;
}
@property Range save()
{
return this;
}
@property bool empty() const
{
return this.dataRange.empty();
}
void popFront()
in (!empty)
in (this.dataRange.front.status == BucketStatus.used)
out (; empty || this.dataRange.back.status == BucketStatus.used)
{
do
{
this.dataRange.popFront();
}
while (!empty && dataRange.front.status != BucketStatus.used);
}
void popBack()
in (!empty)
in (this.dataRange.back.status == BucketStatus.used)
out (; empty || this.dataRange.back.status == BucketStatus.used)
{
do
{
this.dataRange.popBack();
}
while (!empty && dataRange.back.status != BucketStatus.used);
}
@property ref inout(KV) front() inout
in (!empty)
in (this.dataRange.front.status == BucketStatus.used)
{
return this.dataRange.front.kv;
}
@property ref inout(KV) back() inout
in (!empty)
in (this.dataRange.back.status == BucketStatus.used)
{
return this.dataRange.back.kv;
}
Range opIndex()
{
return typeof(return)(this.dataRange[]);
}
Range!(const T) opIndex() const
{
return typeof(return)(this.dataRange[]);
}
}
/**
* Bidirectional range iterating over the key of a $(D_PSYMBOL HashTable).
*
* Params:
* T = Type of the internal hash storage.
*/
struct ByKey(T)
{
private alias Key = CopyConstness!(T, T.Key);
static if (isMutable!T)
{
private alias DataRange = T.array.Range;
}
else
{
private alias DataRange = T.array.ConstRange;
}
private DataRange dataRange;
@disable this();
private this(DataRange dataRange)
{
while (!dataRange.empty && dataRange.front.status != BucketStatus.used)
{
dataRange.popFront();
}
while (!dataRange.empty && dataRange.back.status != BucketStatus.used)
{
dataRange.popBack();
}
this.dataRange = dataRange;
}
@property ByKey save()
{
return this;
}
@property bool empty() const
{
return this.dataRange.empty();
}
@property void popFront()
in (!empty)
in (this.dataRange.front.status == BucketStatus.used)
out (; empty || this.dataRange.back.status == BucketStatus.used)
{
do
{
this.dataRange.popFront();
}
while (!empty && dataRange.front.status != BucketStatus.used);
}
@property void popBack()
in (!empty)
in (this.dataRange.back.status == BucketStatus.used)
out (; empty || this.dataRange.back.status == BucketStatus.used)
{
do
{
this.dataRange.popBack();
}
while (!empty && dataRange.back.status != BucketStatus.used);
}
@property ref inout(Key) front() inout
in (!empty)
in (this.dataRange.front.status == BucketStatus.used)
{
return this.dataRange.front.key;
}
@property ref inout(Key) back() inout
in (!empty)
in (this.dataRange.back.status == BucketStatus.used)
{
return this.dataRange.back.key;
}
ByKey opIndex()
{
return typeof(return)(this.dataRange[]);
}
ByKey!(const T) opIndex() const
{
return typeof(return)(this.dataRange[]);
}
}
/**
* Bidirectional range iterating over the key of a $(D_PSYMBOL HashTable).
*
* Params:
* T = Type of the internal hash storage.
*/
struct ByValue(T)
{
private alias Value = CopyConstness!(T, T.Value);
static if (isMutable!T)
{
private alias DataRange = T.array.Range;
}
else
{
private alias DataRange = T.array.ConstRange;
}
private DataRange dataRange;
@disable this();
private this(DataRange dataRange)
{
while (!dataRange.empty && dataRange.front.status != BucketStatus.used)
{
dataRange.popFront();
}
while (!dataRange.empty && dataRange.back.status != BucketStatus.used)
{
dataRange.popBack();
}
this.dataRange = dataRange;
}
@property ByValue save()
{
return this;
}
@property bool empty() const
{
return this.dataRange.empty();
}
@property void popFront()
in (!empty)
in (this.dataRange.front.status == BucketStatus.used)
out (; empty || this.dataRange.back.status == BucketStatus.used)
{
do
{
this.dataRange.popFront();
}
while (!empty && dataRange.front.status != BucketStatus.used);
}
@property void popBack()
in (!empty)
in (this.dataRange.back.status == BucketStatus.used)
out (; empty || this.dataRange.back.status == BucketStatus.used)
{
do
{
this.dataRange.popBack();
}
while (!empty && dataRange.back.status != BucketStatus.used);
}
@property ref inout(Value) front() inout
in (!empty)
in (this.dataRange.front.status == BucketStatus.used)
{
return this.dataRange.front.kv.value;
}
@property ref inout(Value) back() inout
in (!empty)
in (this.dataRange.back.status == BucketStatus.used)
{
return this.dataRange.back.kv.value;
}
ByValue opIndex()
{
return typeof(return)(this.dataRange[]);
}
ByValue!(const T) opIndex() const
{
return typeof(return)(this.dataRange[]);
}
}
/**
* Hash table is a data structure that stores pairs of keys and values without
* any particular order.
*
* This $(D_PSYMBOL HashTable) is implemented using closed hashing. Hash
* collisions are resolved with linear probing.
*
* $(D_PARAM Key) should be hashable with $(D_PARAM hasher). $(D_PARAM hasher)
* is a callable that accepts an argument of type $(D_PARAM Key) and returns a
* hash value for it ($(D_KEYWORD size_t)).
*
* Params:
* Key = Key type.
* Value = Value type.
* hasher = Hash function for $(D_PARAM Key).
*/
struct HashTable(Key, Value, alias hasher = hash)
if (isHashFunction!(hasher, Key))
{
private alias HashArray = .HashArray!(hasher, Key, Value);
private alias Buckets = HashArray.Buckets;
private HashArray data;
/// Type of the key-value pair stored in the hash table.
alias KeyValue = HashArray.Bucket.KV;
/// The range types for $(D_PSYMBOL HashTable).
alias Range = .Range!HashArray;
/// ditto
alias ConstRange = .Range!(const HashArray);
/// ditto
alias ByKey = .ByKey!(const HashArray);
/// ditto
alias ByValue = .ByValue!HashArray;
/// ditto
alias ConstByValue = .ByValue!(const HashArray);
invariant (this.data.lengthIndex < primes.length);
/**
* Constructor.
*
* Params:
* n = Minimum number of buckets.
* allocator = Allocator.
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
this(size_t n, shared Allocator allocator = defaultAllocator)
in (allocator !is null)
{
this(allocator);
this.data.rehash(n);
}
///
@nogc nothrow pure @safe unittest
{
auto hashTable = HashTable!(string, int)(5);
assert(hashTable.capacity == 7);
}
/// ditto
this(shared Allocator allocator)
in (allocator !is null)
{
this.data = HashArray(allocator);
}
/**
* Initializes this $(D_PARAM HashTable) from another one.
*
* If $(D_PARAM init) is passed by reference, it will be copied.
* If $(D_PARAM init) is passed by value, it will be moved.
*
* Params:
* 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 == HashTable))
in (allocator !is null)
{
this.data = HashArray(init.data, allocator);
}
/// ditto
this(S)(S init, shared Allocator allocator = defaultAllocator)
if (is(S == HashTable))
in (allocator !is null)
{
this.data.move(init.data, allocator);
}
/**
* Constructs the hash table 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 && is(ElementType!R == KeyValue) && !isInfinite!R)
in (allocator !is null)
{
this(allocator);
insert(range);
}
///
@nogc nothrow pure @safe unittest
{
alias KeyValue = HashTable!(string, int).KeyValue;
KeyValue[2] range = [KeyValue("one", 1), KeyValue("two", 2)];
auto hashTable = HashTable!(string, int)(range[]);
assert(hashTable["one"] == 1);
assert(hashTable["two"] == 2);
}
/**
* Initializes the hash table from a static array.
*
* Params:
* n = Array size.
* array = Static array.
* allocator = Allocator.
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
this(size_t n)(KeyValue[n] array,
shared Allocator allocator = defaultAllocator)
in (allocator !is null)
{
this(allocator);
insert(array[]);
}
///
@nogc nothrow pure @safe unittest
{
alias KeyValue = HashTable!(string, int).KeyValue;
auto hashTable = HashTable!(string, int)([KeyValue("one", 1), KeyValue("two", 2)]);
assert(hashTable["one"] == 1);
assert(hashTable["two"] == 2);
}
/**
* Assigns another hash table.
*
* If $(D_PARAM that) is passed by reference, it will be copied.
* If $(D_PARAM that) is passed by value, it will be moved.
*
* Params:
* S = Content type.
* that = The value should be assigned.
*
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) opAssign(S)(ref S that)
if (is(Unqual!S == HashTable))
{
this.data = that.data;
return this;
}
/// ditto
ref typeof(this) opAssign(S)(S that) @trusted
if (is(S == HashTable))
{
this.data.swap(that.data);
return this;
}
/**
* Returns: Used allocator.
*
* Postcondition: $(D_INLINECODE allocator !is null)
*/
@property shared(Allocator) allocator() const
out (allocator; allocator !is null)
{
return this.data.array.allocator;
}
/**
* Maximum amount of elements this $(D_PSYMBOL HashTable) can hold without
* resizing and rehashing. Note that it doesn't mean that the
* $(D_PSYMBOL Set) will hold $(I exactly) $(D_PSYMBOL capacity) elements.
* $(D_PSYMBOL capacity) tells the size of the container under a best-case
* distribution of elements.
*
* Returns: $(D_PSYMBOL HashTable) capacity.
*/
@property size_t capacity() const
{
return this.data.capacity;
}
///
@nogc nothrow pure @safe unittest
{
HashTable!(string, int) hashTable;
assert(hashTable.capacity == 0);
hashTable["eight"] = 8;
assert(hashTable.capacity == 3);
}
/**
* Returns the number of elements in the container.
*
* Returns: The number of elements in the container.
*/
@property size_t length() const
{
return this.data.length;
}
///
@nogc nothrow pure @safe unittest
{
HashTable!(string, int) hashTable;
assert(hashTable.length == 0);
hashTable["eight"] = 8;
assert(hashTable.length == 1);
}
/**
* 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
{
HashTable!(string, int) hashTable;
assert(hashTable.empty);
hashTable["five"] = 5;
assert(!hashTable.empty);
}
/**
* Removes all elements.
*/
void clear()
{
this.data.clear();
}
///
@nogc nothrow pure @safe unittest
{
HashTable!(string, int) hashTable;
hashTable["five"] = 5;
assert(!hashTable.empty);
hashTable.clear();
assert(hashTable.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];
/**
* Inserts a new value at $(D_PARAM key) or reassigns the element if
* $(D_PARAM key) already exists in the hash table.
*
* Params:
* key = The key to insert the value at.
* value = The value to be inserted.
*
* Returns: Just inserted element.
*/
ref Value opIndexAssign()(auto ref Value value, auto ref Key key)
{
auto e = ((ref v) @trusted => &this.data.insert(v))(key);
if (e.status != BucketStatus.used)
{
static if (__traits(isRef, key))
{
e.key = key;
}
else
{
e.moveKey(key);
}
}
static if (__traits(isRef, value))
{
return e.kv.value = value;
}
else
{
return e.kv.value = move(value);
}
}
///
@nogc nothrow pure @safe unittest
{
HashTable!(string, int) hashTable;
assert("Pachycephalosaurus" !in hashTable);
hashTable["Pachycephalosaurus"] = 6;
assert(hashTable.length == 1);
assert("Pachycephalosaurus" in hashTable);
hashTable["Pachycephalosaurus"] = 6;
assert(hashTable.length == 1);
assert("Pachycephalosaurus" in hashTable);
}
/**
* Inserts a new element in the hash table.
*
* If the element with the same key was already in the table, it reassigns
* it with the new value, but $(D_PSYMBOL insert) returns `0`. Otherwise
* `1` is returned.
*
* Params:
* keyValue = Key/value pair.
*
* Returns: The number of the inserted elements with a unique key.
*/
size_t insert()(ref KeyValue keyValue)
{
auto e = ((ref v) @trusted => &this.data.insert(v))(keyValue.key);
size_t inserted;
if (e.status != BucketStatus.used)
{
e.key = keyValue.key;
inserted = 1;
}
e.kv.value = keyValue.value;
return inserted;
}
/// ditto
size_t insert()(KeyValue keyValue)
{
auto e = ((ref v) @trusted => &this.data.insert(v))(keyValue.key);
size_t inserted;
if (e.status != BucketStatus.used)
{
e.moveKey(keyValue.key);
inserted = 1;
}
move(keyValue.value, e.kv.value);
return inserted;
}
///
@nogc nothrow pure @safe unittest
{
HashTable!(string, int) hashTable;
assert(hashTable.insert(hashTable.KeyValue("number", 1)) == 1);
assert(hashTable["number"] == 1);
assert(hashTable.insert(hashTable.KeyValue("number", 2)) == 0);
assert(hashTable["number"] == 2);
}
/**
* Inserts a forward range of key/value pairs into the hash table.
*
* If some of the elements in the $(D_PARAM range) have the same key, they
* are reassigned but are not counted as inserted elements. So the value
* returned by this function will be less than the range length.
*
* Params:
* R = Range type.
* range = Forward range.
*
* Returns: The number of the inserted elements with a unique key.
*/
size_t insert(R)(scope R range)
if (isForwardRange!R && is(ElementType!R == KeyValue) && !isInfinite!R)
{
return foldl!((acc, x) => acc + insert(x))(range, 0U);
}
///
@nogc nothrow pure @safe unittest
{
HashTable!(string, int) hashTable;
hashTable.KeyValue[2] range = [
hashTable.KeyValue("one", 1),
hashTable.KeyValue("two", 2),
];
assert(hashTable.insert(range[]) == 2);
assert(hashTable["one"] == 1);
assert(hashTable["two"] == 2);
}
/**
* Find the element with the key $(D_PARAM key).
*
* Params:
* T = Type comparable with the key type, used for the lookup.
* key = The key to be find.
*
* Returns: The value associated with $(D_PARAM key).
*
* Precondition: Element with $(D_PARAM key) is in this hash table.
*/
ref Value opIndex(T)(auto ref const T key)
if (ifTestable!(T, a => Key.init == a))
{
const code = this.data.locateBucket(key);
for (auto range = this.data.array[code .. $]; !range.empty; range.popFront())
{
if (key == range.front.key)
{
return range.front.kv.value;
}
}
assert(false, "Range violation");
}
///
@nogc nothrow pure @safe unittest
{
HashTable!(string, int) hashTable;
hashTable["Triceratops"] = 7;
assert(hashTable["Triceratops"] == 7);
}
/**
* Removes the element with the key $(D_PARAM key).
*
* The method returns the number of elements removed. Since
* the hash table contains only unique keys, $(D_PARAM remove) always
* returns `1` if an element with the $(D_PARAM key) was found, `0`
* otherwise.
*
* Params:
* key = The key to be removed.
*
* Returns: Number of the removed elements.
*/
size_t remove(Key key)
{
return this.data.remove(key);
}
///
@nogc nothrow pure @safe unittest
{
HashTable!(string, int) hashTable;
hashTable["Euoplocephalus"] = 6;
assert("Euoplocephalus" in hashTable);
assert(hashTable.remove("Euoplocephalus") == 1);
assert(hashTable.remove("Euoplocephalus") == 0);
assert("Euoplocephalus" !in hashTable);
}
/**
* Looks for $(D_PARAM key) in this hash table.
*
* Params:
* T = Type comparable with the key type, used for the lookup.
* key = The key to look for.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM key) exists in the hash table,
* $(D_KEYWORD false) otherwise.
*/
bool opBinaryRight(string op : "in", T)(auto ref const T key) const
if (ifTestable!(T, a => Key.init == a))
{
return key in this.data;
}
///
@nogc nothrow pure @safe unittest
{
HashTable!(string, int) hashTable;
assert("Shantungosaurus" !in hashTable);
hashTable["Shantungosaurus"] = 15;
assert("Shantungosaurus" in hashTable);
assert("Ceratopsia" !in hashTable);
}
/**
* 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 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 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.
*
* Params:
* n = Minimum number of buckets.
*/
void rehash(size_t n)
{
this.data.rehash(n);
}
/**
* Returns a bidirectional range whose element type is a tuple of a key and
* the respective value.
*
* Returns: A bidirectional range that iterates over the container.
*/
Range opIndex()
{
return typeof(return)(this.data.array[]);
}
/// ditto
ConstRange opIndex() const
{
return typeof(return)(this.data.array[]);
}
///
@nogc nothrow pure @safe unittest
{
HashTable!(string, int) hashTable;
assert(hashTable[].empty);
hashTable["Iguanodon"] = 9;
assert(!hashTable[].empty);
assert(hashTable[].front == hashTable.KeyValue("Iguanodon", 9));
assert(hashTable[].back == hashTable.KeyValue("Iguanodon", 9));
}
/**
* Returns a bidirectional range that iterats over the keys of this
* $(D_PSYMBOL HashTable).
*
* This function always returns a $(D_KEYWORD const) range, since changing
* a key of a hash table would probably change its hash value and require
* rehashing.
*
* Returns: $(D_KEYWORD const) bidirectional range that iterates over the
* keys of the container.
*
* See_Also: $(D_PSYMBOL byValue).
*/
ByKey byKey() const
{
return typeof(return)(this.data.array[]);
}
///
@nogc nothrow pure @safe unittest
{
HashTable!(string, int) hashTable;
hashTable["one"] = 1;
hashTable["two"] = 2;
auto byKey = hashTable.byKey();
assert(!byKey.empty);
assert(byKey.front == "one" || byKey.front == "two");
assert(byKey.back == "one" || byKey.back == "two");
assert(byKey.front != byKey.back);
byKey.popFront();
assert(byKey.front == byKey.back);
byKey.popBack();
assert(byKey.empty);
}
/**
* Returns a bidirectional range that iterats over the values of this
* $(D_PSYMBOL HashTable).
*
* Returns: A bidirectional range that iterates over the values of the
* container.
*
* See_Also: $(D_PSYMBOL byKey).
*/
ByValue byValue()
{
return typeof(return)(this.data.array[]);
}
/// ditto
ConstByValue byValue() const
{
return typeof(return)(this.data.array[]);
}
///
@nogc nothrow pure @safe unittest
{
HashTable!(string, int) hashTable;
hashTable["one"] = 1;
hashTable["two"] = 2;
auto byValue = hashTable.byValue();
assert(!byValue.empty);
assert(byValue.front == 1 || byValue.front == 2);
assert(byValue.back == 1 || byValue.back == 2);
assert(byValue.front != byValue.back);
byValue.popFront();
assert(byValue.front == byValue.back);
byValue.popBack();
assert(byValue.empty);
}
}
@nogc nothrow pure @safe unittest
{
auto dinos = HashTable!(string, int)(17);
assert(dinos.empty);
dinos["Ornithominus"] = 4;
dinos["Tyrannosaurus"] = 12;
dinos["Deinonychus"] = 3;
dinos["Stegosaurus"] = 6;
dinos["Brachiosaurus"] = 25;
assert(dinos.length == 5);
assert(dinos["Ornithominus"] == 4);
assert(dinos["Stegosaurus"] == 6);
assert(dinos["Deinonychus"] == 3);
assert(dinos["Tyrannosaurus"] == 12);
assert(dinos["Brachiosaurus"] == 25);
dinos.clear();
assert(dinos.empty);
}

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-2019.
* 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.hashtable;
public import tanya.container.list;
public import tanya.container.queue;
public import tanya.container.set;
public import tanya.container.string;
/**
* 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 tanya.algorithm.mutation;
import tanya.container.entry;
import tanya.exception;
import tanya.memory;
import tanya.meta.trait;
/**
* 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; !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-2019.
* 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,28 +15,31 @@
*/
module tanya.container.set;
import tanya.algorithm.mutation;
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;
@ -65,66 +68,42 @@ struct Range(E)
return this.dataRange.empty();
}
@property void popFront()
in
{
assert(!this.dataRange.empty);
assert(this.dataRange.front.status == BucketStatus.used);
}
out
{
assert(this.dataRange.empty
|| this.dataRange.back.status == BucketStatus.used);
}
body
void popFront()
in (!empty)
in (this.dataRange.front.status == BucketStatus.used)
out (; empty || this.dataRange.back.status == BucketStatus.used)
{
do
{
dataRange.popFront();
this.dataRange.popFront();
}
while (!dataRange.empty && dataRange.front.status != BucketStatus.used);
while (!empty && dataRange.front.status != BucketStatus.used);
}
@property void popBack()
in
{
assert(!this.dataRange.empty);
assert(this.dataRange.back.status == BucketStatus.used);
}
out
{
assert(this.dataRange.empty
|| this.dataRange.back.status == BucketStatus.used);
}
body
void popBack()
in (!empty)
in (this.dataRange.back.status == BucketStatus.used)
out (; empty || this.dataRange.back.status == BucketStatus.used)
{
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
in (!empty)
in (this.dataRange.front.status == BucketStatus.used)
{
assert(!this.dataRange.empty);
assert(this.dataRange.front.status == BucketStatus.used);
}
body
{
return dataRange.front.content;
return this.dataRange.front.key;
}
@property ref inout(E) back() inout
in
in (!empty)
in (this.dataRange.back.status == BucketStatus.used)
{
assert(!this.dataRange.empty);
assert(this.dataRange.back.status == BucketStatus.used);
}
body
{
return dataRange.back.content;
return this.dataRange.back.key;
}
Range opIndex()
@ -132,7 +111,7 @@ struct Range(E)
return typeof(return)(this.dataRange[]);
}
Range!(const E) opIndex() const
Range!(const T) opIndex() const
{
return typeof(return)(this.dataRange[]);
}
@ -145,26 +124,31 @@ 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.
* 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))
{
private alias HashArray = .HashArray!(hasher, T);
private alias Buckets = HashArray.Buckets;
private HashArray data;
/// The range types for $(D_PSYMBOL Set).
alias Range = .Range!T;
alias Range = .Range!HashArray;
/// ditto
alias ConstRange = .Range!(const T);
alias ConstRange = .Range!(const HashArray);
invariant
{
assert(this.lengthIndex < primes.length);
assert(this.data.length == 0
|| this.data.length == primes[this.lengthIndex]);
}
invariant (this.data.lengthIndex < primes.length);
invariant (this.data.array.length == 0
|| this.data.array.length == primes[this.data.lengthIndex]);
/**
* Constructor.
@ -175,39 +159,25 @@ struct Set(T)
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
this(const size_t n, shared Allocator allocator = defaultAllocator)
in
{
assert(allocator !is null);
}
body
this(size_t n, shared Allocator allocator = defaultAllocator)
in (allocator !is null)
{
this(allocator);
rehash(n);
this.data.rehash(n);
}
///
@nogc nothrow pure @safe unittest
{
auto set = Set!int(5);
assert(set.capacity == 7);
}
/// ditto
this(shared Allocator allocator)
in
in (allocator !is null)
{
assert(allocator !is null);
}
body
{
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);
}
/**
@ -220,30 +190,78 @@ 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))
in
if (is(Unqual!S == Set))
in (allocator !is null)
{
assert(allocator !is null);
}
body
{
this.data = typeof(this.data)(init.data, allocator);
this.data = HashArray(init.data, allocator);
}
/// ditto
this(S)(S init, shared Allocator allocator = defaultAllocator)
if (is(S == Set))
in
if (is(S == Set))
in (allocator !is null)
{
assert(allocator !is null);
this.data.move(init.data, allocator);
}
body
/**
* 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 (allocator !is null)
{
this.data = typeof(this.data)(move(init.data), allocator);
this.lengthIndex = init.lengthIndex;
init.lengthIndex = 0;
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 (allocator !is null)
{
this(allocator);
insert(array[]);
}
///
@nogc nothrow pure @safe unittest
{
Set!int set = Set!int([1, 2]);
assert(1 in set);
assert(2 in set);
}
/**
@ -259,19 +277,17 @@ struct Set(T)
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) opAssign(S)(ref S that)
if (is(Unqual!S == Set))
if (is(Unqual!S == Set))
{
this.data = that.data;
this.lengthIndex = that.lengthIndex;
return this;
}
/// ditto
ref typeof(this) opAssign(S)(S that) @trusted
if (is(S == Set))
if (is(S == Set))
{
swap(this.data, that.data);
swap(this.lengthIndex, that.lengthIndex);
this.data.swap(that.data);
return this;
}
@ -281,13 +297,9 @@ struct Set(T)
* Postcondition: $(D_INLINECODE allocator !is null)
*/
@property shared(Allocator) allocator() const
out (allocator)
out (allocator; allocator !is null)
{
assert(allocator !is null);
}
body
{
return cast(shared Allocator) this.data.allocator;
return this.data.array.allocator;
}
/**
@ -301,11 +313,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);
@ -321,19 +333,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);
@ -342,81 +346,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.
*
@ -424,31 +410,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)
{
auto e = ((ref v) @trusted => &this.data.insert(v))(value);
if (e.status != BucketStatus.used)
{
if (this.primes.length == (this.lengthIndex + 1))
{
throw make!HashContainerFullException(defaultAllocator,
"Set is full");
}
rehashToSize(this.lengthIndex + 1);
e.key = value;
return 1;
}
return status == InsertStatus.added;
return 0;
}
///
unittest
@nogc nothrow pure @safe unittest
{
Set!int set;
assert(8 !in set);
@ -465,6 +451,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.
*
@ -476,31 +496,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);
@ -510,30 +515,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;
@ -547,18 +542,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.
@ -566,62 +558,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
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);
@ -630,90 +589,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 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));
}

View File

@ -17,7 +17,7 @@
* Internally $(D_PSYMBOL String) is represented by a sequence of
* $(D_KEYWORD char)s.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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)
@ -26,22 +26,16 @@
*/
module tanya.container.string;
import std.algorithm.comparison;
import std.algorithm.mutation : bringToFront, copy;
import std.algorithm.searching;
static import std.range;
import tanya.algorithm.comparison;
import tanya.algorithm.mutation;
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.array;
import tanya.range.primitive;
version (unittest)
{
import tanya.test.assertion;
}
/**
* Thrown on encoding errors.
*/
@ -76,22 +70,15 @@ if (is(Unqual!E == char))
private alias ContainerType = CopyConstness!(E, String);
private ContainerType* container;
invariant
{
assert(this.begin <= this.end);
assert(this.container !is null);
assert(this.begin >= this.container.data);
assert(this.end <= this.container.data + this.container.length);
}
invariant (this.begin <= this.end);
invariant (this.container !is null);
invariant (this.begin >= this.container.data);
invariant (this.end <= this.container.data + this.container.length);
private this(ref ContainerType container, E* begin, E* end) @trusted
in
{
assert(begin <= end);
assert(begin >= container.data);
assert(end <= container.data + container.length);
}
body
in (begin <= end)
in (begin >= container.data)
in (end <= container.data + container.length)
{
this.container = &container;
this.begin = begin;
@ -118,51 +105,31 @@ if (is(Unqual!E == char))
alias opDollar = length;
@property ref inout(E) front() inout
in
{
assert(!empty);
}
body
in (!empty)
{
return *this.begin;
}
@property ref inout(E) back() inout @trusted
in
{
assert(!empty);
}
body
in (!empty)
{
return *(this.end - 1);
}
void popFront() @trusted
in
{
assert(!empty);
}
body
in (!empty)
{
++this.begin;
}
void popBack() @trusted
in
{
assert(!empty);
}
body
in (!empty)
{
--this.end;
}
ref inout(E) opIndex(const size_t i) inout @trusted
in
{
assert(i < length);
}
body
in (i < length)
{
return *(this.begin + i);
}
@ -178,23 +145,15 @@ if (is(Unqual!E == char))
}
ByCodeUnit opSlice(const size_t i, const size_t j) @trusted
in
{
assert(i <= j);
assert(j <= length);
}
body
in (i <= j)
in (j <= length)
{
return typeof(return)(*this.container, this.begin + i, this.begin + j);
}
ByCodeUnit!(const E) opSlice(const size_t i, const size_t j) const @trusted
in
{
assert(i <= j);
assert(j <= length);
}
body
in (i <= j)
in (j <= length)
{
return typeof(return)(*this.container, this.begin + i, this.begin + j);
}
@ -218,22 +177,15 @@ if (is(Unqual!E == char))
private alias ContainerType = CopyConstness!(E, String);
private ContainerType* container;
invariant
{
assert(this.begin <= this.end);
assert(this.container !is null);
assert(this.begin >= this.container.data);
assert(this.end <= this.container.data + this.container.length);
}
invariant (this.begin <= this.end);
invariant (this.container !is null);
invariant (this.begin >= this.container.data);
invariant (this.end <= this.container.data + this.container.length);
private this(ref ContainerType container, E* begin, E* end) @trusted
in
{
assert(begin <= end);
assert(begin >= container.data);
assert(end <= container.data + container.length);
}
body
in (begin <= end)
in (begin >= container.data)
in (end <= container.data + container.length)
{
this.container = &container;
this.begin = begin;
@ -253,15 +205,8 @@ if (is(Unqual!E == char))
}
@property dchar front() const @trusted
in
{
assert(!empty);
}
out (chr)
{
assert(chr < 0xd800 || chr > 0xdfff);
}
body
in (!empty)
out (chr; chr < 0xd800 || chr > 0xdfff)
{
dchar chr;
ubyte units;
@ -291,11 +236,7 @@ if (is(Unqual!E == char))
}
void popFront() @trusted
in
{
assert(!empty);
}
body
in (!empty)
{
ubyte units;
if ((*begin & 0xf0) == 0xf0)
@ -341,10 +282,7 @@ struct String
private char* data;
private size_t capacity_;
pure nothrow @safe @nogc invariant
{
assert(this.length_ <= this.capacity_);
}
@nogc nothrow pure @safe invariant (this.length_ <= this.capacity_);
/**
* Constructs the string from a stringish range.
@ -434,11 +372,7 @@ struct String
/// ditto
this(shared Allocator allocator) @nogc nothrow pure @safe
in
{
assert(allocator !is null);
}
body
in (allocator !is null)
{
this.allocator_ = allocator;
}
@ -499,12 +433,6 @@ struct String
}
}
@nogc pure @safe unittest
{
auto s = String(0, 'K');
assert(s.length == 0);
}
this(this) @nogc nothrow pure @trusted
{
auto buf = this.data[0 .. this.length_];
@ -523,12 +451,8 @@ struct String
private void write4Bytes(ref const dchar src)
@nogc nothrow pure @trusted
in
{
assert(capacity - length >= 4);
assert(src - 0x10000 < 0x100000);
}
body
in (capacity - length >= 4)
in (src - 0x10000 < 0x100000)
{
auto dst = this.data + length;
@ -542,11 +466,7 @@ struct String
private size_t insertWideChar(C)(auto ref const C chr) @trusted
if (is(C == wchar) || is(C == dchar))
in
{
assert(capacity - length >= 3);
}
body
in (capacity - length >= 3)
{
auto dst = this.data + length;
if (chr < 0x80)
@ -579,16 +499,10 @@ struct String
* Params:
* chr = The character should be inserted.
*
* Returns: The number of bytes inserted.
*
* Throws: $(D_PSYMBOL UTFException).
* Returns: The number of bytes inserted (1).
*/
size_t insertBack(const char chr) @nogc pure @trusted
size_t insertBack(char chr) @nogc nothrow pure @trusted
{
if ((chr & 0x80) != 0)
{
throw defaultAllocator.make!UTFException("Invalid UTF-8 character");
}
reserve(length + 1);
*(data + length) = chr;
@ -610,13 +524,6 @@ struct String
return ret;
}
// Allocates enough space for 3-byte character.
@nogc pure @safe unittest
{
String s;
s.insertBack('\u8100');
}
/// ditto
size_t insertBack(const dchar chr) @nogc pure @trusted
{
@ -638,12 +545,6 @@ struct String
}
}
@nogc pure @safe unittest
{
assertThrown!UTFException(() => String(1, cast(dchar) 0xd900));
assertThrown!UTFException(() => String(1, cast(wchar) 0xd900));
}
/**
* Inserts a stringish range at the end of the string.
*
@ -652,8 +553,6 @@ struct String
* str = String should be inserted.
*
* Returns: The number of bytes inserted.
*
* Throws: $(D_PSYMBOL UTFException).
*/
size_t insertBack(R)(R str) @trusted
if (!isInfinite!R
@ -673,46 +572,18 @@ struct String
this.length_ = size;
return str.length;
}
else static if (isInstanceOf!(ByCodeUnit, R))
{
str.get.copy(this.data[length .. size]);
this.length_ = size;
return str.length;
}
else
{
size_t insertedLength;
while (!str.empty)
foreach (c; str)
{
ubyte expectedLength;
if ((str.front & 0x80) == 0x00)
{
expectedLength = 1;
}
else if ((str.front & 0xe0) == 0xc0)
{
expectedLength = 2;
}
else if ((str.front & 0xf0) == 0xe0)
{
expectedLength = 3;
}
else if ((str.front & 0xf8) == 0xf0)
{
expectedLength = 4;
}
else
{
throw defaultAllocator.make!UTFException("Invalid UTF-8 sequeunce");
}
size = length + expectedLength;
reserve(size);
for (; expectedLength > 0; --expectedLength)
{
if (str.empty)
{
throw defaultAllocator.make!UTFException("Invalid UTF-8 sequeunce");
}
*(data + length) = str.front;
str.popFront();
}
insertedLength += expectedLength;
this.length_ = size;
insertedLength += insertBack(c);
}
return insertedLength;
}
@ -759,7 +630,7 @@ struct String
}
dchar d = (range[0] - 0xd800) | ((range[1] - 0xdc00) >> 10);
std.range.popFrontN(range, 2);
popFrontN(range, 2);
}
else
{
@ -828,7 +699,7 @@ struct String
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
String s;
assert(s.capacity == 0);
@ -869,7 +740,7 @@ struct String
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
auto s = String("Die Alten lasen laut.");
assert(s.capacity == 21);
@ -894,7 +765,7 @@ struct String
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
auto s = String("In allem Schreiben ist Schamlosigkeit.");
assert(s.capacity == 38);
@ -918,13 +789,9 @@ struct String
const size_t i,
const size_t j)
if (is(Unqual!R == char))
in
{
assert(i <= j);
assert(j <= length);
assert(j - i == value.length);
}
body
in (i <= j)
in (j <= length)
in (j - i == value.length)
{
auto target = opSlice(i, j);
copy(value, target);
@ -936,12 +803,8 @@ struct String
const size_t i,
const size_t j)
@nogc nothrow pure @trusted
in
{
assert(i <= j);
assert(j <= length);
}
body
in (i <= j)
in (j <= length)
{
copy(value[], this.data[i .. j]);
return opSlice(i, j);
@ -952,12 +815,8 @@ struct String
const size_t i,
const size_t j)
@nogc nothrow pure @trusted
in
{
assert(i <= j);
assert(j <= length);
}
body
in (i <= j)
in (j <= length)
{
for (auto p = this.data + i; p < this.data + j; ++p)
{
@ -991,7 +850,7 @@ struct String
*
* Returns: Null-terminated string.
*/
const(char)* toStringz() @nogc nothrow pure
const(char)* toStringz() @nogc nothrow pure @system
{
reserve(length + 1);
this.data[length] = '\0';
@ -999,7 +858,7 @@ struct String
}
///
@nogc pure unittest
@nogc nothrow pure @system unittest
{
auto s = String("C string.");
assert(s.toStringz()[0] == 'C');
@ -1018,7 +877,7 @@ struct String
alias opDollar = length;
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
auto s = String("Piscis primuin a capite foetat.");
assert(s.length == 31);
@ -1034,17 +893,13 @@ struct String
* Precondition: $(D_INLINECODE length > pos).
*/
ref inout(char) opIndex(const size_t pos) inout @nogc nothrow pure @trusted
in
{
assert(length > pos);
}
body
in (length > pos)
{
return *(this.data + pos);
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
auto s = String("Alea iacta est.");
assert(s[0] == 'A');
@ -1067,7 +922,7 @@ struct String
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
auto s = String("Plutarchus");
auto r = s[];
@ -1086,7 +941,7 @@ struct String
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
auto s = const String("Was ich vermag, soll gern geschehen. Goethe");
auto r1 = s[];
@ -1162,7 +1017,7 @@ struct String
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
String s;
assert(s.empty);
@ -1183,12 +1038,8 @@ struct String
*/
ByCodeUnit!char opSlice(const size_t i, const size_t j)
@nogc nothrow pure @trusted
in
{
assert(i <= j);
assert(j <= length);
}
body
in (i <= j)
in (j <= length)
{
return typeof(return)(this, this.data + i, this.data + j);
}
@ -1196,18 +1047,14 @@ struct String
/// ditto
ByCodeUnit!(const char) opSlice(const size_t i, const size_t j)
const @nogc nothrow pure @trusted
in
{
assert(i <= j);
assert(j <= length);
}
body
in (i <= j)
in (j <= length)
{
return typeof(return)(this, this.data + i, this.data + j);
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
auto s = String("Vladimir Soloviev");
auto r = s[9 .. $];
@ -1271,7 +1118,7 @@ struct String
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
auto s = String("Черная, потом пропахшая выть!");
s = String("Как мне тебя не ласкать, не любить?");
@ -1299,10 +1146,11 @@ struct String
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
auto s = String("Оловом светится лужная голь...");
s = "Грустная песня, ты - русская боль.";
assert(s == "Грустная песня, ты - русская боль.");
}
/**
@ -1318,33 +1166,33 @@ struct String
int opCmp(S)(auto ref S that) const @trusted
if (is(Unqual!S == String))
{
return cmp(this.data[0 .. length], that.data[0 .. that.length]);
return compare(this.data[0 .. length], that.data[0 .. that.length]);
}
/// ditto
int opCmp(S)(ByCodeUnit!S that) const @trusted
if (is(Unqual!S == char))
{
return cmp(this.data[0 .. length],
that.begin[0 .. that.end - that.begin]);
return compare(this.data[0 .. length],
that.begin[0 .. that.end - that.begin]);
}
/// ditto
int opCmp(S)(ByCodePoint!S that) const @trusted
if (is(Unqual!S == char))
{
return cmp(this.data[0 .. length],
that.begin[0 .. that.end - that.begin]);
return compare(this.data[0 .. length],
that.begin[0 .. that.end - that.begin]);
}
/// ditto
int opCmp()(const char[] that) const @trusted
{
return cmp(this.data[0 .. length], that);
return compare(this.data[0 .. length], that);
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
assert(String("Голубая кофта.") < String("Синие глаза."));
assert(String("Никакой я правды") < String("милой не сказал")[]);
@ -1397,7 +1245,7 @@ struct String
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
assert(String("Милая спросила:") != String("Крутит ли метель?"));
assert(String("Затопить бы печку,") != String("постелить постель.")[]);
@ -1430,7 +1278,7 @@ struct String
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
auto s = String("alea iacta est.");
@ -1455,40 +1303,18 @@ struct String
return opSliceAssign(value, 0, length);
}
@nogc pure @safe unittest
{
auto s1 = String("Buttercup");
auto s2 = String("Cap");
s2[] = s1[6 .. $];
assert(s2 == "cup");
}
/// ditto
ByCodeUnit!char opIndexAssign(const char value) @nogc nothrow pure @safe
{
return opSliceAssign(value, 0, length);
}
@nogc pure @safe unittest
{
auto s1 = String("Wow");
s1[] = 'a';
assert(s1 == "aaa");
}
/// ditto
ByCodeUnit!char opIndexAssign(const char[] value) @nogc nothrow pure @safe
{
return opSliceAssign(value, 0, length);
}
@nogc pure @safe unittest
{
auto s1 = String("ö");
s1[] = "oe";
assert(s1 == "oe");
}
/**
* Remove all characters beloning to $(D_PARAM r).
*
@ -1503,13 +1329,9 @@ struct String
*/
R remove(R)(R r) @trusted
if (is(R == ByCodeUnit!char) || is(R == ByCodePoint!char))
in
{
assert(r.container is &this);
assert(r.begin >= this.data);
assert(r.end <= this.data + length);
}
body
in (r.container is &this)
in (r.begin >= this.data)
in (r.end <= this.data + length)
{
auto end = this.data + this.length;
copy(ByCodeUnit!char(this, r.end, end), ByCodeUnit!char(this, r.begin, end));
@ -1520,6 +1342,8 @@ struct String
///
@nogc pure @safe unittest
{
import tanya.algorithm.searching : count;
auto s = String("Из пословицы слова не выкинешь.");
assert(s.remove(s[5 .. 24]).length == 33);
@ -1527,7 +1351,7 @@ struct String
assert(s.length == 38);
auto byCodePoint = s.byCodePoint();
std.range.popFrontN(byCodePoint, 8);
popFrontN(byCodePoint, 8);
assert(s.remove(byCodePoint).count == 0);
assert(s == "Из слова");
@ -1556,23 +1380,20 @@ struct String
&& isInputRange!T
&& isSomeChar!(ElementType!T)))
&& (is(R == ByCodeUnit!char) || is(R == ByCodePoint!char)))
in
in (r.container is &this)
in (r.begin >= this.data)
in (r.end <= this.data + length)
{
assert(r.container is &this);
assert(r.begin >= this.data);
assert(r.end <= this.data + length);
}
body
{
auto oldLen = this.data + length;
const oldLength = length;
const after = r.end - this.data;
const inserted = insertBack(el);
bringToFront(ByCodeUnit!char(this, r.end, oldLen),
ByCodeUnit!char(this, oldLen, this.data + length));
rotate(this.data[after .. oldLength], this.data[oldLength .. length]);
return inserted;
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
auto s = String("Казнить нельзя помиловать.");
s.insertAfter(s[0 .. 27], ",");
@ -1589,19 +1410,15 @@ struct String
&& isInputRange!T
&& isSomeChar!(ElementType!T)))
&& (is(R == ByCodeUnit!char) || is(R == ByCodePoint!char)))
in
{
assert(r.container is &this);
assert(r.begin >= this.data);
assert(r.end <= this.data + length);
}
body
in (r.container is &this)
in (r.begin >= this.data)
in (r.end <= this.data + length)
{
return insertAfter(R(this, this.data, r.begin), el);
}
///
@nogc pure @safe unittest
@nogc nothrow pure @safe unittest
{
auto s = String("Казнить нельзя помиловать.");
s.insertBefore(s[27 .. $], ",");
@ -1612,69 +1429,15 @@ struct String
assert(s == "Казнить, нельзя помиловать.");
}
/**
* Calculates the hash value for the string.
*
* Returns: Hash value for the string.
*/
size_t toHash() const @nogc nothrow pure @safe
{
return hash(get);
}
mixin DefaultAllocator;
}
// Postblit works.
@nogc 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));
}

View File

@ -5,7 +5,7 @@
/**
* This module provides functions for converting between different types.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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,233 +14,14 @@
*/
module tanya.conv;
import std.traits : Unsigned;
import tanya.container.string;
import tanya.format;
import tanya.memory;
import tanya.memory.op;
import tanya.memory.allocator;
deprecated("Use tanya.memory.lifetime.emplace instead")
public import tanya.memory.lifetime : emplace;
import tanya.meta.trait;
import tanya.meta.transform;
version (unittest)
{
import tanya.test.assertion;
}
/**
* 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)());
}
body
{
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)());
}body
{
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
{
import tanya.memory : stateSize;
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);
}
body
{
auto result = (() @trusted => cast(T*) memory.ptr)();
static if (Args.length == 1)
{
*result = T(args[0]);
}
else
{
*result = T.init;
}
return result;
}
/// 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);
}
body
{
auto result = (() @trusted => cast(T*) memory.ptr)();
static if (!hasElaborateAssign!T && isAssignable!T)
{
*result = T.init;
}
else
{
static const T init = T.init;
copy((cast(void*) &init)[0 .. T.sizeof], memory);
}
static if (Args.length == 0)
{
static assert(is(typeof({ static T t; })),
"Default constructor is disabled");
}
else static if (is(typeof(T(args))))
{
*result = T(args);
}
else static if (is(typeof(result.__ctor(args))))
{
result.__ctor(args);
}
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);
}
// 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))));
}
import tanya.range;
/**
* Thrown if a type conversion fails.
@ -263,6 +44,96 @@ 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 (base >= 2)
in (base <= 36)
{
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
@ -302,12 +173,6 @@ template to(To)
static assert(is(typeof(val) == int));
}
@nogc nothrow pure @safe 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).
@ -370,83 +235,6 @@ if (isIntegral!From
}
}
@nogc nothrow pure @safe unittest
{
// 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);
}
@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);
}
@nogc pure @safe unittest
{
assertThrown!ConvException(&to!(short, int), int.min);
assertThrown!ConvException(&to!(short, int), int.max);
assertThrown!ConvException(&to!(ushort, uint), uint.max);
assertThrown!ConvException(&to!(uint, int), -1);
}
@nogc nothrow pure @safe unittest
{
enum Test : int
{
one,
two,
}
assert(Test.one.to!int == 0);
assert(Test.two.to!int == 1);
}
/**
* Converts a floating point number to an integral type.
*
@ -489,13 +277,6 @@ if (isFloatingPoint!From
assert(2147483646.5.to!uint == 2147483646);
}
@nogc pure @safe unittest
{
assertThrown!ConvException(&to!(int, double), 2147483647.5);
assertThrown!ConvException(&to!(int, double), -2147483648.5);
assertThrown!ConvException(&to!(uint, double), -21474.5);
}
/**
* Performs checked conversion from an integral type $(D_PARAM From) to an
* $(D_KEYWORD enum).
@ -537,16 +318,6 @@ if (isIntegral!From && is(To == enum))
assert(1.to!Test == Test.two);
}
@nogc pure @safe unittest
{
enum Test : uint
{
one,
two,
}
assertThrown!ConvException(&to!(Test, int), 5);
}
/**
* Converts $(D_PARAM from) to a boolean.
*
@ -598,12 +369,6 @@ if (isNumeric!From && is(Unqual!To == bool) && !is(Unqual!To == Unqual!From))
assert(1.to!bool);
}
@nogc pure @safe unittest
{
assertThrown!ConvException(&to!(bool, int), -1);
assertThrown!ConvException(&to!(bool, int), 2);
}
/// ditto
To to(To, From)(auto ref const From from)
if ((is(From == String) || isSomeString!From) && is(Unqual!To == bool))
@ -630,11 +395,6 @@ if ((is(From == String) || isSomeString!From) && is(Unqual!To == bool))
}
@nogc pure @safe unittest
{
assertThrown!ConvException(() => "1".to!bool);
}
/**
* Converts a boolean to $(D_PARAM To).
*
@ -680,30 +440,126 @@ if (is(Unqual!From == bool) && isNumeric!To && !is(Unqual!To == Unqual!From))
}
/**
* Converts $(D_PARAM From) to a $(D_PSYMBOL String).
* Converts a stringish range to an integral value.
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_PARAM from) converted to $(D_PSYMBOL String).
* Returns: $(D_PARAM from) converted to $(D_PARAM To).
*
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) doesn't contain an
* integral value.
*/
To to(To, From)(auto ref From from)
if (is(Unqual!To == String))
if (isInputRange!From && isSomeChar!(ElementType!From) && isIntegral!To)
{
return format!"{}"(from);
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,
"Negative integer overflow");
}
else
{
negative = true;
from.popFront();
}
}
if (from.empty)
{
throw make!ConvException(defaultAllocator, "Input range is empty");
}
ubyte base = 10;
if (from.front == '0')
{
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;
}
}
auto unsigned = readIntegral!(Unsigned!To, From)(from, base);
if (!from.empty)
{
throw make!ConvException(defaultAllocator, "Integer overflow");
}
static if (isSigned!To)
{
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 nothrow pure @safe unittest
@nogc pure @safe unittest
{
assert(true.to!String == "true");
assert(false.to!String == "false");
}
assert("1234".to!uint() == 1234);
assert("1234".to!int() == 1234);
assert("1234".to!int() == 1234);
@nogc nothrow pure @safe unittest
{
static assert(is(typeof((const String("true")).to!bool)));
static assert(is(typeof(false.to!(const String) == "false")));
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);
}

View File

@ -1,66 +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/. */
/**
* Common exceptions and errors.
*
* 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/exception.d,
* tanya/exception.d)
*/
module tanya.exception;
import tanya.conv;
import tanya.memory;
/**
* Error thrown if memory allocation fails.
*/
final class OutOfMemoryError : Error
{
/**
* Constructs new error.
*
* 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 = "Out of memory",
string file = __FILE__,
size_t line = __LINE__,
Throwable next = null) @nogc nothrow pure @safe
{
super(msg, file, line, next);
}
/// ditto
this(string msg,
Throwable next,
string file = __FILE__,
size_t line = __LINE__) @nogc nothrow pure @safe
{
super(msg, file, line, next);
}
}
/**
* Allocates $(D_PSYMBOL OutOfMemoryError) in a static storage and throws it.
*
* Params:
* msg = Custom error message.
*
* Throws: $(D_PSYMBOL OutOfMemoryError).
*/
void onOutOfMemoryError(string msg = "Out of memory")
@nogc nothrow pure @trusted
{
static ubyte[stateSize!OutOfMemoryError] memory;
alias PureType = OutOfMemoryError function(string) @nogc nothrow pure;
throw (cast(PureType) () => emplace!OutOfMemoryError(memory))(msg);
}

2468
source/tanya/format.d Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,293 +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 functions for converting between different types.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: Jeff Roberts, $(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)
*/
module tanya.format.conv;
import tanya.container.string;
import tanya.memory;
import tanya.memory.op;
import tanya.meta.trait;
import tanya.meta.transform;
version (unittest)
{
import tanya.test.assertion;
}
/**
* Thrown if a type conversion fails.
*/
final class ConvException : 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);
}
}
/**
* 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
* $(D_PARAM To), just returns $(D_PARAM from).
*
* Params:
* To = Target type.
*
* Returns: $(D_PARAM from).
*/
deprecated("Use tanya.conv.to instead")
template to(To)
{
/**
* Params:
* From = Source type.
* from = Source value.
*/
ref To to(From)(ref From from)
if (is(To == From))
{
return from;
}
/// ditto
To to(From)(From from)
if (is(Unqual!To == Unqual!From) || (isNumeric!From && isFloatingPoint!To))
{
return from;
}
}
/**
* Performs checked conversion from an integral type $(D_PARAM From) to an
* integral type $(D_PARAM To).
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_PARAM from) converted to $(D_PARAM To).
*
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) is too small or too
* large to be represented by $(D_PARAM To).
*/
deprecated("Use tanya.conv.to instead")
To to(To, From)(From from)
if (isIntegral!From
&& isIntegral!To
&& !is(Unqual!To == Unqual!From)
&& !is(To == enum))
{
static if ((isUnsigned!From && isSigned!To && From.sizeof == To.sizeof)
|| From.sizeof > To.sizeof)
{
if (from > To.max)
{
throw make!ConvException(defaultAllocator,
"Positive integer overflow");
}
}
static if (isSigned!From)
{
static if (isUnsigned!To)
{
if (from < 0)
{
throw make!ConvException(defaultAllocator,
"Negative integer overflow");
}
}
else static if (From.sizeof > To.sizeof)
{
if (from < To.min)
{
throw make!ConvException(defaultAllocator,
"Negative integer overflow");
}
}
}
static if (From.sizeof <= To.sizeof)
{
return from;
}
else static if (isSigned!To)
{
return cast(To) from;
}
else
{
return from & To.max;
}
}
/**
* Converts $(D_PARAM from) to a boolean.
*
* If $(D_PARAM From) is a numeric type, then `1` becomes $(D_KEYWORD true),
* `0` $(D_KEYWORD false). Otherwise $(D_PSYMBOL ConvException) is thrown.
*
* If $(D_PARAM To) is a string (built-in string or $(D_PSYMBOL String)),
* then `"true"` or `"false"` are converted to the appropriate boolean value.
* Otherwise $(D_PSYMBOL ConvException) is thrown.
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_KEYWORD from) converted to a boolean.
*
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) isn't convertible.
*/
deprecated("Use tanya.conv.to instead")
To to(To, From)(From from)
if (isNumeric!From && is(Unqual!To == bool) && !is(Unqual!To == Unqual!From))
{
if (from == 0)
{
return false;
}
else if (from < 0)
{
throw make!ConvException(defaultAllocator,
"Negative number overflow");
}
else if (from <= 1)
{
return true;
}
throw make!ConvException(defaultAllocator,
"Positive number overflow");
}
/// ditto
deprecated("Use tanya.conv.to instead")
To to(To, From)(auto ref const From from)
if ((is(From == String) || isSomeString!From) && is(Unqual!To == bool))
{
if (from == "true")
{
return true;
}
else if (from == "false")
{
return false;
}
throw make!ConvException(defaultAllocator,
"String doesn't contain a boolean value");
}
/**
* Converts a boolean to $(D_PARAM To).
*
* If $(D_PARAM To) is a numeric type, then $(D_KEYWORD true) becomes `1`,
* $(D_KEYWORD false) `0`.
*
* If $(D_PARAM To) is a $(D_PSYMBOL String), then `"true"` or `"false"`
* is returned.
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_PARAM from) converted to $(D_PARAM To).
*/
deprecated("Use tanya.conv.to instead")
To to(To, From)(const From from)
if (is(Unqual!From == bool) && isNumeric!To && !is(Unqual!To == Unqual!From))
{
return from;
}
/// ditto
deprecated("Use tanya.conv.to instead")
To to(To, From)(const From from)
if (is(Unqual!From == bool) && is(Unqual!To == String))
{
return String(from ? "true" : "false");
}
/**
* 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).
*/
deprecated("Use tanya.conv.to instead")
To to(To, From)(From from)
if (isFloatingPoint!From
&& isIntegral!To
&& !is(Unqual!To == Unqual!From)
&& !is(To == enum))
{
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;
}
/**
* 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).
*/
deprecated("Use tanya.conv.to instead")
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 ~ "'");
}

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-2019.
* 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

@ -0,0 +1,15 @@
/* 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/. */
/**
* Copyright: Eugene Wissner 2018-2019.
* 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/package.d,
* tanya/hash/package.d)
*/
module tanya.hash;
public import tanya.hash.lookup;

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
/**
* Number theory.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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,11 +14,18 @@
*/
module tanya.math.nbtheory;
import tanya.math.mp;
import tanya.meta.trait;
import tanya.meta.transform;
version (TanyaNative)
{
private extern float fabs(float) @nogc nothrow pure @safe;
private extern double fabs(double) @nogc nothrow pure @safe;
private extern real fabs(real) @nogc nothrow pure @safe;
private extern double log(double) @nogc nothrow pure @safe;
private extern float logf(float) @nogc nothrow pure @safe;
private extern real logl(real) @nogc nothrow pure @safe;
}
else
{
@ -30,15 +37,15 @@ else
* Calculates the absolute value of a number.
*
* Params:
* I = Value type.
* x = Value.
* T = Argument type.
* x = Argument.
*
* Returns: Absolute value of $(D_PARAM x).
*/
I abs(I)(I x)
if (isIntegral!I)
Unqual!T abs(T)(T x)
if (isIntegral!T)
{
static if (isSigned!I)
static if (isSigned!T)
{
return x >= 0 ? x : -x;
}
@ -49,7 +56,7 @@ if (isIntegral!I)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
int i = -1;
assert(i.abs == 1);
@ -60,28 +67,15 @@ pure nothrow @safe @nogc unittest
static assert(is(typeof(u.abs) == uint));
}
version (D_Ddoc)
/// ditto
Unqual!T abs(T)(T x)
if (isFloatingPoint!T)
{
/// ditto
I abs(I)(I x)
if (isFloatingPoint!I);
}
else version (TanyaNative)
{
extern I abs(I)(I number) pure nothrow @safe @nogc
if (isFloatingPoint!I);
}
else
{
I abs(I)(I x)
if (isFloatingPoint!I)
{
return fabs(cast(real) x);
}
return fabs(x);
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
float f = -1.64;
assert(f.abs == 1.64F);
@ -96,58 +90,50 @@ pure nothrow @safe @nogc unittest
static assert(is(typeof(r.abs) == real));
}
/// ditto
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;
}
version (D_Ddoc)
{
/**
* Calculates natural logarithm of $(D_PARAM x).
*
* Params:
* T = Argument type.
* x = Argument.
*
* Returns: Natural logarithm of $(D_PARAM x).
*/
float ln(float x) pure nothrow @safe @nogc;
/// ditto
double ln(double x) pure nothrow @safe @nogc;
/// ditto
real ln(real x) pure nothrow @safe @nogc;
Unqual!T ln(T)(T x)
if (isFloatingPoint!T);
}
else version (TanyaNative)
{
extern float ln(float x) pure nothrow @safe @nogc;
extern double ln(double x) pure nothrow @safe @nogc;
extern real ln(real x) pure nothrow @safe @nogc;
Unqual!T ln(T)(T x) @nogc nothrow pure @safe
if (isFloatingPoint!T)
{
static if (is(Unqual!T == float))
{
return logf(x);
}
else static if (is(Unqual!T == double))
{
return log(x);
}
else
{
return logl(x);
}
}
}
else
{
float ln(float x) pure nothrow @safe @nogc
Unqual!T ln(T)(T x)
if (isFloatingPoint!T)
{
return log(x);
}
double ln(double x) pure nothrow @safe @nogc
{
return log(x);
}
alias ln = log;
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
import tanya.math;

View File

@ -12,7 +12,7 @@
* be found in its submodules. $(D_PSYMBOL tanya.math) doesn't import any
* submodules publically, they should be imported explicitly.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2019.
* 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)
@ -21,8 +21,6 @@
*/
module tanya.math;
import tanya.algorithm.mutation;
import tanya.math.mp;
import tanya.math.nbtheory;
import tanya.meta.trait;
import tanya.meta.transform;
@ -38,7 +36,7 @@ enum IEEEPrecision : ubyte
/**
* Tests the precision of floating-point type $(D_PARAM F).
*
* For $(D_KEYWORD float), $(D_PSYMBOL ieeePrecision) always evaluates to
* 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.
@ -87,9 +85,9 @@ if (isFloatingPoint!F)
static assert(ieeePrecision!double == IEEEPrecision.double_);
}
private union FloatBits(F)
package(tanya) union FloatBits(F)
{
F floating;
Unqual!F floating;
static if (ieeePrecision!F == IEEEPrecision.single)
{
uint integral;
@ -255,21 +253,6 @@ if (isFloatingPoint!F)
assert(classify(-real.infinity) == FloatingPointClass.infinite);
}
@nogc nothrow pure @safe unittest
{
static if (ieeePrecision!float == IEEEPrecision.doubleExtended)
{
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);
}
}
/**
* Determines whether $(D_PARAM x) is a finite number.
*
@ -396,7 +379,7 @@ if (isFloatingPoint!F)
/**
* 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
*
@ -459,7 +442,7 @@ if (isFloatingPoint!F)
/**
* Determines whether $(D_PARAM x) is a normilized number or not.
*
* Normalized number is a number that can be represented as
*
* <pre>
@ -564,9 +547,6 @@ if (isFloatingPoint!F)
/**
* 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.
@ -582,11 +562,7 @@ if (isFloatingPoint!F)
*/
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.");
}
body
in (z > 0, "Division by zero")
{
G mask = G.max / 2 + 1;
H result;
@ -620,46 +596,6 @@ 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;
}
///
@nogc nothrow pure @safe unittest
{
@ -673,225 +609,3 @@ body
assert(pow(0, 0, 5) == 1);
assert(pow(0, 5, 5) == 0);
}
///
@nogc nothrow pure @safe 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) @nogc nothrow pure @safe
{
return pow(2, x - 1, x) == 1;
}
///
@nogc nothrow pure @safe unittest
{
assert(74623.isPseudoprime);
assert(104729.isPseudoprime);
assert(15485867.isPseudoprime);
assert(!15485868.isPseudoprime);
}
@nogc nothrow pure @safe unittest
{
assert(74653.isPseudoprime);
assert(74687.isPseudoprime);
assert(74699.isPseudoprime);
assert(74707.isPseudoprime);
assert(74713.isPseudoprime);
assert(74717.isPseudoprime);
assert(74719.isPseudoprime);
assert(74747.isPseudoprime);
assert(74759.isPseudoprime);
assert(74761.isPseudoprime);
assert(74771.isPseudoprime);
assert(74779.isPseudoprime);
assert(74797.isPseudoprime);
assert(74821.isPseudoprime);
assert(74827.isPseudoprime);
assert(9973.isPseudoprime);
assert(49979693.isPseudoprime);
assert(104395303.isPseudoprime);
assert(593441861.isPseudoprime);
assert(104729.isPseudoprime);
assert(15485867.isPseudoprime);
assert(49979693.isPseudoprime);
assert(104395303.isPseudoprime);
assert(593441861.isPseudoprime);
assert(899809363.isPseudoprime);
assert(982451653.isPseudoprime);
}
/**
* Determines minimum of two numbers.
*
* Params:
* x = First number.
* y = Second number.
*
* Returns: $(D_PARAM x) if $(D_PARAM x) is smaller than $(D_PSYMBOL y),
* $(D_PARAM y) otherwise.
*
* See_Also: $(D_PSYMBOL max).
*/
T min(T)(T x, T y)
if (isIntegral!T)
{
return x < y ? x : y;
}
///
@nogc nothrow pure @safe unittest
{
assert(min(5, 3) == 3);
assert(min(4, 4) == 4);
}
/// ditto
T min(T)(T x, T y)
if (isFloatingPoint!T)
{
if (isNaN(x))
{
return y;
}
if (isNaN(y))
{
return x;
}
return x < y ? x : y;
}
///
@nogc nothrow pure @safe unittest
{
assert(min(5.2, 3.0) == 3.0);
assert(min(5.2, double.nan) == 5.2);
assert(min(double.nan, 3.0) == 3.0);
assert(isNaN(min(double.nan, double.nan)));
}
/// ditto
ref T min(T)(ref T x, ref T y)
if (is(Unqual!T == Integer))
{
return x < y ? x : y;
}
/// ditto
T min(T)(T x, T y)
if (is(T == Integer))
{
return x < y ? move(x) : move(y);
}
///
@nogc nothrow pure @safe unittest
{
assert(min(Integer(5), Integer(3)) == 3);
}
/**
* Determines maximum of two numbers.
*
* Params:
* x = First number.
* y = Second number.
*
* Returns: $(D_PARAM x) if $(D_PARAM x) is larger than $(D_PSYMBOL y),
* $(D_PARAM y) otherwise.
*
* See_Also: $(D_PSYMBOL min).
*/
T max(T)(T x, T y)
if (isIntegral!T)
{
return x > y ? x : y;
}
///
@nogc nothrow pure @safe unittest
{
assert(max(5, 3) == 5);
assert(max(4, 4) == 4);
}
/// ditto
T max(T)(T x, T y)
if (isFloatingPoint!T)
{
if (isNaN(x))
{
return y;
}
if (isNaN(y))
{
return x;
}
return x > y ? x : y;
}
///
@nogc nothrow pure @safe unittest
{
assert(max(5.2, 3.0) == 5.2);
assert(max(5.2, double.nan) == 5.2);
assert(max(double.nan, 3.0) == 3.0);
assert(isNaN(max(double.nan, double.nan)));
}
/// ditto
ref T max(T)(ref T x, ref T y)
if (is(Unqual!T == Integer))
{
return x > y ? x : y;
}
/// ditto
T max(T)(T x, T y)
if (is(T == Integer))
{
return x > y ? move(x) : move(y);
}
///
@nogc nothrow pure @safe unittest
{
assert(max(Integer(5), Integer(3)) == 5);
}
// min/max accept const and mutable references.
@nogc nothrow pure @safe unittest
{
{
Integer i1 = 5, i2 = 3;
assert(min(i1, i2) == 3);
assert(max(i1, i2) == 5);
}
{
const Integer i1 = 5, i2 = 3;
assert(min(i1, i2) == 3);
assert(max(i1, i2) == 5);
}
}

View File

@ -5,7 +5,7 @@
/**
* Random number generator.
*
* Copyright: Eugene Wissner 2016.
* Copyright: Eugene Wissner 2016-2019.
* 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,8 @@
*/
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 +35,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 +52,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 +72,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 +85,40 @@ 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;
Option!ubyte poll(out ubyte[maxGather] output) @nogc
out (length; length.isNothing || length.get <= maxGather);
}
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,18 +149,14 @@ 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
out (length)
{
assert(length <= maxGather);
}
body
override Option!ubyte poll(out ubyte[maxGather] output) @nogc nothrow
{
// int getrandom(void *buf, size_t buflen, unsigned int flags);
auto length = syscall(318, output.ptr, output.length, 0);
Nullable!ubyte ret;
import mir.linux._asm.unistd : NR_getrandom;
auto length = syscall(NR_getrandom, output.ptr, output.length, 0);
Option!ubyte ret;
if (length >= 0)
{
@ -147,190 +165,162 @@ version (linux)
return ret;
}
}
}
else version (SecureARC4Random)
{
private extern(C) void arc4random_buf(scope void* buf, size_t nbytes)
@nogc nothrow @system;
version (X86_64)
/**
* Uses arc4random_buf.
*/
class PlatformEntropySource : EntropySource
{
private unittest
/**
* Returns: Minimum bytes required from the entropy source.
*/
override @property ubyte threshold() const @nogc nothrow pure @safe
{
auto entropy = defaultAllocator.make!Entropy();
ubyte[blockSize] output;
output = entropy.random;
return 32;
}
defaultAllocator.dispose(entropy);
/**
* Returns: Whether this entropy source is strong.
*/
override @property bool strong() const @nogc nothrow pure @safe
{
return true;
}
/**
* Poll the entropy source.
*
* Params:
* 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.
*/
override Option!ubyte poll(out ubyte[maxGather] output)
@nogc nothrow @safe
{
(() @trusted => arc4random_buf(output.ptr, output.length))();
return Option!ubyte(cast(ubyte) (output.length));
}
}
}
/**
* Pseudorandom number generator.
* ---
* auto entropy = defaultAllocator.make!Entropy();
*
* ubyte[blockSize] output;
*
* output = entropy.random;
*
* defaultAllocator.dispose(entropy);
* ---
*/
class Entropy
else version (Windows)
{
/// Entropy sources.
protected EntropySource[] sources;
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;
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.
*/
this(const size_t maxSources = 20,
shared Allocator allocator = defaultAllocator) @nogc
in
private extern(Windows) @nogc nothrow
{
assert(maxSources > 0 && maxSources <= ubyte.max);
assert(allocator !is null);
BOOL CryptGenRandom(HCRYPTPROV, DWORD, PBYTE);
BOOL CryptAcquireContextA(HCRYPTPROV*, LPCSTR, LPCSTR, DWORD, DWORD);
BOOL CryptAcquireContextW(HCRYPTPROV*, LPCWSTR, LPCWSTR, DWORD, DWORD);
BOOL CryptReleaseContext(HCRYPTPROV, ULONG_PTR);
}
body
{
allocator.resize(sources, maxSources);
version (linux)
private bool initCryptGenRandom(scope ref HCRYPTPROV hProvider)
@nogc nothrow @trusted
{
// 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))
{
this ~= allocator.make!PlatformEntropySource;
}
}
/**
* Returns: Amount of the registered entropy sources.
*/
@property ubyte sourceCount() const pure nothrow @safe @nogc
{
return sourceCount_;
}
/**
* Add an entropy source.
*
* Params:
* source = Entropy source.
*
* Returns: $(D_PSYMBOL this).
*
* See_Also:
* $(D_PSYMBOL EntropySource)
*/
Entropy opOpAssign(string op)(EntropySource source)
pure nothrow @safe @nogc
if (op == "~")
in
{
assert(sourceCount_ <= sources.length);
}
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;
// Run through our entropy sources
for (ubyte i; i < sourceCount; ++i)
if (GetLastError() != NTE_BAD_KEYSET)
{
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;
return false;
}
// Attempt to create default container
if (!CryptAcquireContextA(&hProvider,
null,
null,
PROV_RSA_FULL,
CRYPT_NEWKEYSET | CRYPT_SILENT))
{
return false;
}
}
while (++done < 256);
if (!haveStrong)
{
throw allocator.make!EntropyException("No strong entropy source defined.");
}
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; i < sourceCount; ++i)
{
sources[i].size = 0;
}
return output;
return true;
}
/**
* Update entropy accumulator.
*
* Params:
* sourceId = Entropy source index in $(D_PSYMBOL sources).
* data = Data got from the entropy source.
* length = Length of the received data.
*/
protected void update(in ubyte sourceId,
ref ubyte[maxGather] data,
ubyte length) pure nothrow @safe @nogc
class PlatformEntropySource : EntropySource
{
ubyte[2] header;
private HCRYPTPROV hProvider;
if (length > blockSize)
/**
* Uses CryptGenRandom.
*/
this() @nogc
{
data[0 .. 64] = sha512Of(data);
length = blockSize;
if (!initCryptGenRandom(hProvider))
{
throw defaultAllocator.make!EntropyException("CryptAcquireContextW failed.");
}
assert(hProvider > 0, "hProvider not properly initialized.");
}
header[0] = sourceId;
header[1] = length;
~this() @nogc nothrow @safe
{
if (hProvider > 0)
{
(() @trusted => CryptReleaseContext(hProvider, 0))();
}
}
accumulator.put(header);
accumulator.put(data[0 .. length]);
/**
* 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:
* 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.
*/
override Option!ubyte poll(out ubyte[maxGather] output)
@nogc nothrow @safe
{
Option!ubyte ret;
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;
}

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

@ -0,0 +1,210 @@
/* 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-2019.
* 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 (TanyaNative)
{
import mir.linux._asm.unistd;
import tanya.sys.linux.syscall;
import tanya.sys.posix.ioctl;
import tanya.sys.posix.net.if_;
import tanya.sys.posix.socket;
}
else 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 (TanyaNative)
{
if (name.length >= IF_NAMESIZE)
{
return 0;
}
ifreq ifreq_ = void;
copy(name, ifreq_.ifr_name[]);
ifreq_.ifr_name[name.length] = '\0';
auto socket = syscall(AF_INET,
SOCK_DGRAM | SOCK_CLOEXEC,
0,
NR_socket);
if (socket <= 0)
{
return 0;
}
scope (exit)
{
syscall(socket, NR_close);
}
if (syscall(socket,
SIOCGIFINDEX,
cast(ptrdiff_t) &ifreq_,
NR_ioctl) == 0)
{
return ifreq_.ifr_ifindex;
}
return 0;
}
else 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 (TanyaNative)
{
ifreq ifreq_ = void;
ifreq_.ifr_ifindex = index;
auto socket = syscall(AF_INET,
SOCK_DGRAM | SOCK_CLOEXEC,
0,
NR_socket);
if (socket <= 0)
{
return String();
}
scope (exit)
{
syscall(socket, NR_close);
}
if (syscall(socket,
SIOCGIFNAME,
cast(ptrdiff_t) &ifreq_,
NR_ioctl) == 0)
{
return String(findNullTerminated(ifreq_.ifr_name));
}
return String();
}
else 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));
}
}

View File

@ -5,7 +5,7 @@
/**
* Internet utilities.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2019.
* 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,10 +14,9 @@
*/
module tanya.net.inet;
import std.math;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range.primitive;
import tanya.range;
/**
* Represents an unsigned integer as an $(D_KEYWORD ubyte) range.
@ -31,7 +30,7 @@ import tanya.range.primitive;
* L = Desired range length.
*/
struct NetworkOrder(uint L)
if (L > ubyte.sizeof && L <= ulong.sizeof)
if (L > ubyte.sizeof && L <= ulong.sizeof)
{
static if (L > uint.sizeof)
{
@ -53,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);
}
@ -69,15 +68,11 @@ 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)
if (isUnsigned!T)
in
{
assert(value <= pow(2, L * 8) - 1);
}
body
this(T)(T value)
if (isUnsigned!T)
in (value <= (2 ^^ (L * 8)) - 1)
{
this.value = value & StorageType.max;
}
@ -88,11 +83,7 @@ struct NetworkOrder(uint L)
* Precondition: $(D_INLINECODE length > 0).
*/
@property ubyte back() const
in
{
assert(this.length > 0);
}
body
in (this.length > 0)
{
return this.value & 0xff;
}
@ -103,11 +94,7 @@ struct NetworkOrder(uint L)
* Precondition: $(D_INLINECODE length > 0).
*/
@property ubyte front() const
in
{
assert(this.length > 0);
}
body
in (this.length > 0)
{
return (this.value >> ((this.length - 1) * 8)) & 0xff;
}
@ -118,11 +105,7 @@ struct NetworkOrder(uint L)
* Precondition: $(D_INLINECODE length > 0).
*/
void popBack()
in
{
assert(this.length > 0);
}
body
in (this.length > 0)
{
this.value >>= 8;
--this.size;
@ -134,11 +117,7 @@ struct NetworkOrder(uint L)
* Precondition: $(D_INLINECODE length > 0).
*/
void popFront()
in
{
assert(this.length > 0);
}
body
in (this.length > 0)
{
this.value &= StorageType.max >> ((StorageType.sizeof - this.length) * 8);
--this.size;
@ -170,7 +149,7 @@ struct NetworkOrder(uint L)
}
///
pure nothrow @safe @nogc unittest
@nogc nothrow pure @safe unittest
{
auto networkOrder = NetworkOrder!3(0xae34e2u);
assert(!networkOrder.empty);
@ -190,15 +169,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));
}
/**
* Converts the $(D_KEYWORD ubyte) input range $(D_PARAM range) to
* $(D_PARAM T).
@ -216,10 +186,10 @@ private unittest
* order.
*/
T toHostOrder(T = size_t, R)(R range)
if (isInputRange!R
&& !isInfinite!R
&& is(Unqual!(ElementType!R) == ubyte)
&& isUnsigned!T)
if (isInputRange!R
&& !isInfinite!R
&& is(Unqual!(ElementType!R) == ubyte)
&& isUnsigned!T)
{
T ret;
ushort pos = T.sizeof * 8;
@ -238,7 +208,7 @@ 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);

1331
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-2019.
* 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-2019.
* 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,13 +14,9 @@
*/
module tanya.net.uri;
import tanya.conv;
import tanya.encoding.ascii;
import tanya.memory;
version (unittest)
{
import tanya.test.assertion;
}
import tanya.memory.allocator;
/**
* Thrown if an invalid URI was specified.
@ -37,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);
}
@ -82,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;
@ -152,16 +148,13 @@ 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;
}
this.scheme = source[0 .. pos];
start = pos + 1;
goto ParsePath;
}
}
else if (pos == 0 && parsePort(source[pos .. $]))
@ -305,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;
@ -382,91 +365,6 @@ struct URL
assert(u.fragment == "fragment");
}
@nogc pure @system 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");
}
@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"));
}
// Issue 254: https://issues.caraus.io/issues/254.
@nogc pure @system 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.

View File

@ -5,7 +5,7 @@
/**
* Network programming.
*
* Copyright: Eugene Wissner 2016-2017.
* Copyright: Eugene Wissner 2016-2019.
* 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)

View File

@ -5,7 +5,43 @@
/**
* Low-level socket programming.
*
* Copyright: Eugene Wissner 2016-2017.
* Current API supports only server-side TCP communication.
*
* Here is an example of a cross-platform blocking server:
*
* ---
* import std.stdio;
* import tanya.memory;
* import tanya.network;
*
* void main()
* {
* auto socket = defaultAllocator.make!StreamSocket(AddressFamily.inet);
* auto address = defaultAllocator.make!InternetAddress("127.0.0.1",
* cast(ushort) 8192);
*
* socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true);
* socket.blocking = true;
* socket.bind(address);
* socket.listen(5);
*
* auto client = socket.accept();
* client.send(cast(const(ubyte)[]) "Test\n");
*
* ubyte[100] buf;
* auto response = client.receive(buf[]);
*
* writeln(cast(const(char)[]) buf[0 .. response]);
*
* defaultAllocator.dispose(client);
* defaultAllocator.dispose(socket);
* }
* ---
*
* For an example of an asynchronous server refer to the documentation of the
* $(D_PSYMBOL tanya.async.loop) module.
*
* Copyright: Eugene Wissner 2016-2019.
* 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,11 +52,12 @@ module tanya.network.socket;
import core.stdc.errno;
import core.time;
import std.algorithm.comparison;
public import std.socket : SocketOption, SocketOptionLevel;
import std.traits;
import std.typecons;
import tanya.memory;
import tanya.algorithm.comparison;
import tanya.bitmanip;
import tanya.memory.allocator;
import tanya.meta.trait;
import tanya.os.error;
/// Value returned by socket operations on error.
enum int socketError = -1;
@ -44,10 +81,8 @@ version (Posix)
}
else version (Windows)
{
import core.sys.windows.winbase : ERROR_IO_INCOMPLETE,
ERROR_IO_PENDING,
GetModuleHandle,
GetProcAddress;
import core.sys.windows.winbase;
import core.sys.windows.winerror;
import core.sys.windows.winsock2 : accept,
addrinfo,
bind,
@ -68,6 +103,7 @@ else version (Windows)
send,
setsockopt,
shutdown,
SO_TYPE,
SOCKADDR,
sockaddr,
sockaddr_in,
@ -76,18 +112,10 @@ else version (Windows)
socket,
socklen_t,
SOL_SOCKET,
SO_TYPE,
WSAEWOULDBLOCK,
WSAGetLastError;
import tanya.async.iocp;
import tanya.sys.windows.def;
import tanya.sys.windows.error : ECONNABORTED = WSAECONNABORTED,
ENOBUFS = WSAENOBUFS,
EOPNOTSUPP = WSAEOPNOTSUPP,
EPROTONOSUPPORT = WSAEPROTONOSUPPORT,
EPROTOTYPE = WSAEPROTOTYPE,
ESOCKTNOSUPPORT = WSAESOCKTNOSUPPORT,
ETIMEDOUT = WSAETIMEDOUT,
EWOULDBLOCK = WSAEWOULDBLOCK;
public import tanya.sys.windows.winbase;
public import tanya.sys.windows.winsock2;
@ -185,7 +213,7 @@ else version (Windows)
{
assert(count >= 0);
}
body
do
{
DWORD lpNumber;
BOOL result = GetOverlappedResult(overlapped.handle,
@ -259,7 +287,7 @@ else version (Windows)
{
assert(count >= 0);
}
body
do
{
DWORD lpNumber;
BOOL result = GetOverlappedResult(overlapped.handle,
@ -470,7 +498,7 @@ struct Linger
*
* See_Also: $(D_PSYMBOL time).
*/
@property enabled(const bool value) pure nothrow @safe @nogc
@property void enabled(const bool value) pure nothrow @safe @nogc
{
this.l_onoff = value;
}
@ -581,39 +609,6 @@ enum AddressFamily : int
inet6 = 10, /// IP version 6.
}
/**
* Error codes for $(D_PSYMBOL Socket).
*/
enum SocketError : int
{
/// Unknown error.
unknown = 0,
/// Firewall rules forbid connection.
accessDenied = EPERM,
/// A socket operation was attempted on a non-socket.
notSocket = EBADF,
/// The network is not available.
networkDown = ECONNABORTED,
/// An invalid pointer address was detected by the underlying socket provider.
fault = EFAULT,
/// An invalid argument was supplied to a $(D_PSYMBOL Socket) member.
invalidArgument = EINVAL,
/// The limit on the number of open sockets has been reached.
tooManyOpenSockets = ENFILE,
/// No free buffer space is available for a Socket operation.
noBufferSpaceAvailable = ENOBUFS,
/// The address family is not supported by the protocol family.
operationNotSupported = EOPNOTSUPP,
/// The protocol is not implemented or has not been configured.
protocolNotSupported = EPROTONOSUPPORT,
/// Protocol error.
protocolError = EPROTOTYPE,
/// The connection attempt timed out, or the connected host has failed to respond.
timedOut = ETIMEDOUT,
/// The support for the specified socket type does not exist in this address family.
socketNotSupported = ESOCKTNOSUPPORT,
}
/**
* $(D_PSYMBOL SocketException) should be thrown only if one of the socket functions
* $(D_PSYMBOL socketError) and sets $(D_PSYMBOL errno), because
@ -621,7 +616,7 @@ enum SocketError : int
*/
class SocketException : Exception
{
const SocketError error = SocketError.unknown;
const ErrorCode.ErrorNo error = ErrorCode.ErrorNo.success;
/**
* Params:
@ -637,7 +632,7 @@ class SocketException : Exception
{
super(msg, file, line, next);
foreach (member; EnumMembers!SocketError)
foreach (member; EnumMembers!(ErrorCode.ErrorNo))
{
if (member == lastError)
{
@ -647,24 +642,24 @@ class SocketException : Exception
}
if (lastError == ENOMEM)
{
error = SocketError.noBufferSpaceAvailable;
error = ErrorCode.ErrorNo.noBufferSpace;
}
else if (lastError == EMFILE)
{
error = SocketError.tooManyOpenSockets;
error = ErrorCode.ErrorNo.tooManyDescriptors;
}
else version (linux)
{
if (lastError == ENOSR)
{
error = SocketError.networkDown;
error = ErrorCode.ErrorNo.networkDown;
}
}
else version (Posix)
{
if (lastError == EPROTO)
{
error = SocketError.networkDown;
error = ErrorCode.ErrorNo.networkDown;
}
}
}
@ -720,7 +715,7 @@ abstract class Socket
assert(handle != SocketType.init);
assert(handle_ == SocketType.init, "Socket handle cannot be changed");
}
body
do
{
handle_ = handle;
@ -749,7 +744,7 @@ abstract class Socket
{
assert(handle != SocketType.init);
}
body
do
{
scope (failure)
{
@ -1443,7 +1438,7 @@ bool wouldHaveBlocked() nothrow @trusted @nogc
else version (Windows)
{
return WSAGetLastError() == ERROR_IO_PENDING
|| WSAGetLastError() == EWOULDBLOCK
|| WSAGetLastError() == WSAEWOULDBLOCK
|| WSAGetLastError() == ERROR_IO_INCOMPLETE;
}
}

View File

@ -0,0 +1,204 @@
/* 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-2019.
* 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 (!this.data.empty)
{
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[]));
}

View File

@ -31,7 +31,7 @@
* (D_INLINECODE dchar[])) are treated as any other normal array, they aren't
* auto-decoded.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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)
@ -54,12 +54,8 @@ module tanya.range.array;
*
* Precondition: $(D_INLINECODE array.length > 0).
*/
@property ref T front(T)(T[] array)
in
{
assert(array.length > 0);
}
body
@property ref inout(T) front(T)(return scope inout(T)[] array)
in (array.length > 0)
{
return array[0];
}
@ -94,12 +90,8 @@ body
*
* Precondition: $(D_INLINECODE array.length > 0).
*/
@property ref T back(T)(T[] array)
in
{
assert(array.length > 0);
}
body
@property ref inout(T) back(T)(return scope inout(T)[] array)
in (array.length > 0)
{
return array[$ - 1];
}
@ -133,23 +125,15 @@ body
*
* Precondition: $(D_INLINECODE array.length > 0).
*/
void popFront(T)(ref T[] array)
in
{
assert(array.length > 0);
}
body
void popFront(T)(scope ref inout(T)[] array)
in (array.length > 0)
{
array = array[1 .. $];
}
/// ditto
void popBack(T)(ref T[] array)
in
{
assert(array.length > 0);
}
body
void popBack(T)(scope ref inout(T)[] array)
in (array.length > 0)
{
array = array[0 .. $ - 1];
}
@ -178,7 +162,7 @@ body
* Returns: $(D_KEYWORD true) if $(D_PARAM array) has no elements,
* $(D_KEYWORD false) otherwise.
*/
@property bool empty(T)(const T[] array)
@property bool empty(T)(scope const T[] array)
{
return array.length == 0;
}
@ -203,7 +187,7 @@ body
*
* Returns: A copy of the slice $(D_PARAM array).
*/
@property T[] save(T)(T[] array)
@property inout(T)[] save(T)(return scope inout(T)[] array)
{
return array;
}

View File

@ -6,7 +6,7 @@
* This package contains generic functions and templates to be used with D
* ranges.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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,5 +15,6 @@
*/
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

@ -1,114 +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/. */
/**
* 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/sys/windows/error.d,
* tanya/sys/windows/error.d)
*/
module tanya.sys.windows.error;
version (Windows):
private enum WSABASEERR = 10000;
enum
{
WSAEINTR = WSABASEERR + 4,
WSAEBADF = WSABASEERR + 9,
WSAEACCES = WSABASEERR + 13,
WSAEFAULT = WSABASEERR + 14,
WSAEINVAL = WSABASEERR + 22,
WSAEMFILE = WSABASEERR + 24,
WSAEWOULDBLOCK = WSABASEERR + 35,
WSAEINPROGRESS = WSABASEERR + 36,
WSAEALREADY = WSABASEERR + 37,
WSAENOTSOCK = WSABASEERR + 38,
WSAEDESTADDRREQ = WSABASEERR + 39,
WSAEMSGSIZE = WSABASEERR + 40,
WSAEPROTOTYPE = WSABASEERR + 41,
WSAENOPROTOOPT = WSABASEERR + 42,
WSAEPROTONOSUPPORT = WSABASEERR + 43,
WSAESOCKTNOSUPPORT = WSABASEERR + 44,
WSAEOPNOTSUPP = WSABASEERR + 45,
WSAEPFNOSUPPORT = WSABASEERR + 46,
WSAEAFNOSUPPORT = WSABASEERR + 47,
WSAEADDRINUSE = WSABASEERR + 48,
WSAEADDRNOTAVAIL = WSABASEERR + 49,
WSAENETDOWN = WSABASEERR + 50,
WSAENETUNREACH = WSABASEERR + 51,
WSAENETRESET = WSABASEERR + 52,
WSAECONNABORTED = WSABASEERR + 53,
WSAECONNRESET = WSABASEERR + 54,
WSAENOBUFS = WSABASEERR + 55,
WSAEISCONN = WSABASEERR + 56,
WSAENOTCONN = WSABASEERR + 57,
WSAESHUTDOWN = WSABASEERR + 58,
WSAETOOMANYREFS = WSABASEERR + 59,
WSAETIMEDOUT = WSABASEERR + 60,
WSAECONNREFUSED = WSABASEERR + 61,
WSAELOOP = WSABASEERR + 62,
WSAENAMETOOLONG = WSABASEERR + 63,
WSAEHOSTDOWN = WSABASEERR + 64,
WSAEHOSTUNREACH = WSABASEERR + 65,
WSAENOTEMPTY = WSABASEERR + 66,
WSAEPROCLIM = WSABASEERR + 67,
WSAEUSERS = WSABASEERR + 68,
WSAEDQUOT = WSABASEERR + 69,
WSAESTALE = WSABASEERR + 70,
WSAEREMOTE = WSABASEERR + 71,
WSASYSNOTREADY = WSABASEERR + 91,
WSAVERNOTSUPPORTED = WSABASEERR + 92,
WSANOTINITIALISED = WSABASEERR + 93,
WSAEDISCON = WSABASEERR + 101,
WSAENOMORE = WSABASEERR + 102,
WSAECANCELLED = WSABASEERR + 103,
WSAEINVALIDPROCTABLE = WSABASEERR + 104,
WSAEINVALIDPROVIDER = WSABASEERR + 105,
WSAEPROVIDERFAILEDINIT = WSABASEERR + 106,
WSASYSCALLFAILURE = WSABASEERR + 107,
WSASERVICE_NOT_FOUND = WSABASEERR + 108,
WSATYPE_NOT_FOUND = WSABASEERR + 109,
WSA_E_NO_MORE = WSABASEERR + 110,
WSA_E_CANCELLED = WSABASEERR + 111,
WSAEREFUSED = WSABASEERR + 112,
WSAHOST_NOT_FOUND = WSABASEERR + 1001,
WSATRY_AGAIN = WSABASEERR + 1002,
WSANO_RECOVERY = WSABASEERR + 1003,
WSANO_DATA = WSABASEERR + 1004,
WSA_QOS_RECEIVERS = WSABASEERR + 1005,
WSA_QOS_SENDERS = WSABASEERR + 1006,
WSA_QOS_NO_SENDERS = WSABASEERR + 1007,
WSA_QOS_NO_RECEIVERS = WSABASEERR + 1008,
WSA_QOS_REQUEST_CONFIRMED = WSABASEERR + 1009,
WSA_QOS_ADMISSION_FAILURE = WSABASEERR + 1010,
WSA_QOS_POLICY_FAILURE = WSABASEERR + 1011,
WSA_QOS_BAD_STYLE = WSABASEERR + 1012,
WSA_QOS_BAD_OBJECT = WSABASEERR + 1013,
WSA_QOS_TRAFFIC_CTRL_ERROR = WSABASEERR + 1014,
WSA_QOS_GENERIC_ERROR = WSABASEERR + 1015,
WSA_QOS_ESERVICETYPE = WSABASEERR + 1016,
WSA_QOS_EFLOWSPEC = WSABASEERR + 1017,
WSA_QOS_EPROVSPECBUF = WSABASEERR + 1018,
WSA_QOS_EFILTERSTYLE = WSABASEERR + 1019,
WSA_QOS_EFILTERTYPE = WSABASEERR + 1020,
WSA_QOS_EFILTERCOUNT = WSABASEERR + 1021,
WSA_QOS_EOBJLENGTH = WSABASEERR + 1022,
WSA_QOS_EFLOWCOUNT = WSABASEERR + 1023,
WSA_QOS_EUNKOWNPSOBJ = WSABASEERR + 1024,
WSA_QOS_EPOLICYOBJ = WSABASEERR + 1025,
WSA_QOS_EFLOWDESC = WSABASEERR + 1026,
WSA_QOS_EPSFLOWSPEC = WSABASEERR + 1027,
WSA_QOS_EPSFILTERSPEC = WSABASEERR + 1028,
WSA_QOS_ESDMODEOBJ = WSABASEERR + 1029,
WSA_QOS_ESHAPERATEOBJ = WSABASEERR + 1030,
WSA_QOS_RESERVED_PETYPE = WSABASEERR + 1031,
}

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-2019.
* 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 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 tanya.meta.metafunction;
*
* 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,595 @@ 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);
}
/**
* $(D_PSYMBOL Option) is a type that contains an optional value.
*
* Params:
* T = Type of the encapsulated value.
*
* See_Also: $(D_PSYMBOL option).
*/
struct Option(T)
{
private bool isNothing_ = true;
private T value = void;
/**
* Constructs a new option with $(D_PARAM value).
*
* Params:
* value = Encapsulated value.
*/
this()(ref T value)
{
this.value = value;
this.isNothing_ = false;
}
/// ditto
this()(T value) @trusted
{
moveEmplace(value, this.value);
this.isNothing_ = false;
}
/**
* Tells if the option is just a value or nothing.
*
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Option) contains a nothing,
* $(D_KEYWORD false) if it contains a value.
*/
@property bool isNothing() const
{
return this.isNothing_;
}
/**
* Returns the encapsulated value.
*
* Returns: Value encapsulated in this $(D_PSYMBOL Option).
*
* See_Also: $(D_PSYMBOL or).
*
* Precondition: `!isNothing`.
*/
@property ref inout(T) get() inout
in (!isNothing, "Option is nothing")
{
return this.value;
}
/// ditto
deprecated("Call Option.get explicitly instead of relying on alias this")
@property ref inout(T) get_() inout
in (!isNothing, "Option is nothing")
{
return this.value;
}
/**
* Returns the encapsulated value if available or a default value
* otherwise.
*
* Note that the contained value can be returned by reference only if the
* default value is passed by reference as well.
*
* Params:
* U = Type of the default value.
* defaultValue = Default value.
*
* Returns: The value of this $(D_PSYMBOL Option) if available,
* $(D_PARAM defaultValue) otherwise.
*
* See_Also: $(D_PSYMBOL isNothing), $(D_PSYMBOL get).
*/
@property U or(U)(U defaultValue) inout
if (is(U == T) && isCopyable!T)
{
return isNothing ? defaultValue : this.value;
}
/// ditto
@property ref inout(T) or(ref inout(T) defaultValue) inout
{
return isNothing ? defaultValue : this.value;
}
/**
* Casts this $(D_PSYMBOL Option) to $(D_KEYWORD bool).
*
* An $(D_PSYMBOL Option) is $(D_KEYWORD true) if it contains a value,
* ($D_KEYWORD false) if it contains nothing.
*
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Option) contains a value,
* ($D_KEYWORD false) if it contains nothing.
*/
bool opCast(U : bool)()
{
return !isNothing;
}
/**
* Compares this $(D_PSYMBOL Option) with $(D_PARAM that).
*
* If both objects are options of the same type and they don't contain a
* value, they are considered equal. If only one of them contains a value,
* they aren't equal. Otherwise, the encapsulated values are compared for
* equality.
*
* If $(D_PARAM U) is a type comparable with the type encapsulated by this
* $(D_PSYMBOL Option), the value of this $(D_PSYMBOL Option) is compared
* with $(D_PARAM that), this $(D_PSYMBOL Option) must have a value then.
*
* Params:
* U = Type of the object to compare with.
* that = Object to compare with.
*
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Option) and
* $(D_PARAM that) are equal, $(D_KEYWORD false) if not.
*
* Precondition: `!isNothing` if $(D_PARAM U) is equality comparable with
* $(D_PARAM T).
*/
bool opEquals(U)(auto ref const U that) const
if (is(U == Option))
{
if (!isNothing && !that.isNothing)
{
return this.value == that.value;
}
return isNothing == that.isNothing;
}
/// ditto
bool opEquals(U)(auto ref const U that) const
if (ifTestable!(U, a => a == T.init) && !is(U == Option))
in (!isNothing)
{
return get == that;
}
/**
* Resets this $(D_PSYMBOL Option) and destroys the contained value.
*
* $(D_PSYMBOL reset) can be safely called on an $(D_PSYMBOL Option) that
* doesn't contain any value.
*/
void reset()
{
static if (hasElaborateDestructor!T)
{
destroy(this.value);
}
this.isNothing_ = true;
}
/**
* Assigns a new value.
*
* Params:
* U = Type of the new value.
* that = New value.
*
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) opAssign(U)(ref U that)
if (is(U : T) && !is(U == Option))
{
this.value = that;
this.isNothing_ = false;
return this;
}
/// ditto
ref typeof(this) opAssign(U)(U that)
if (is(U == T))
{
move(that, this.value);
this.isNothing_ = false;
return this;
}
/// ditto
ref typeof(this) opAssign(U)(ref U that)
if (is(U == Option))
{
if (that.isNothing)
{
reset();
}
else
{
this.value = that.get;
this.isNothing_ = false;
}
return this;
}
/// ditto
ref typeof(this) opAssign(U)(U that)
if (is(U == Option))
{
move(that.value, this.value);
this.isNothing_ = that.isNothing_;
return this;
}
version (D_Ddoc)
{
/**
* If $(D_PARAM T) has a `toHash()` method, $(D_PSYMBOL Option) defines
* `toHash()` which returns `T.toHash()` if it is set or 0 otherwise.
*
* Returns: Hash value.
*/
size_t toHash() const;
}
else static if (is(typeof(T.init.toHash()) == size_t))
{
size_t toHash() const
{
return isNothing ? 0U : this.value.toHash();
}
}
alias get_ this;
}
///
@nogc nothrow pure @safe unittest
{
Option!int option;
assert(option.isNothing);
assert(option.or(8) == 8);
option = 5;
assert(!option.isNothing);
assert(option.get == 5);
assert(option.or(8) == 5);
option.reset();
assert(option.isNothing);
}
/**
* Creates a new $(D_PSYMBOL Option).
*
* Params:
* T = Option type.
* value = Initial value.
*
* See_Also: $(D_PSYMBOL Option).
*/
Option!T option(T)(auto ref T value)
{
return Option!T(forward!value);
}
/// ditto
Option!T option(T)()
{
return Option!T();
}
///
@nogc nothrow pure @safe unittest
{
assert(option!int().isNothing);
assert(option(5) == 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 (this.tag == staticIndexOf!(T, Types), "Variant isn't initialized")
{
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);
}

13
sys/dub.json Normal file
View File

@ -0,0 +1,13 @@
{
"name": "sys",
"description": "Low-level operating system bindings and definitions",
"targetType": "library",
"sourcePaths": [
"."
],
"importPaths": [
"."
],
"dflags-dmd": ["-dip1000"]
}

View File

@ -0,0 +1,59 @@
/* 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/. */
/*
* Copyright: Eugene Wissner 2018-2019.
* 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)
*/
module tanya.sys.linux.syscall;
version (TanyaNative):
extern ptrdiff_t syscall(ptrdiff_t, ptrdiff_t)
@nogc nothrow @system;
extern ptrdiff_t syscall(ptrdiff_t, ptrdiff_t, ptrdiff_t)
@nogc nothrow @system;
extern ptrdiff_t syscall(ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t)
@nogc nothrow @system;
extern ptrdiff_t syscall(ptrdiff_t,
ptrdiff_t,
ptrdiff_t,
ptrdiff_t,
ptrdiff_t,
ptrdiff_t,
ptrdiff_t) @nogc nothrow @system;
// Same syscalls as above but pure.
private template getOverloadMangling(size_t n)
{
enum string getOverloadMangling = __traits(getOverloads,
tanya.sys.linux.syscall,
"syscall")[n].mangleof;
}
pragma(mangle, getOverloadMangling!0)
extern ptrdiff_t syscall_(ptrdiff_t, ptrdiff_t)
@nogc nothrow pure @system;
pragma(mangle, getOverloadMangling!1)
extern ptrdiff_t syscall_(ptrdiff_t, ptrdiff_t, ptrdiff_t)
@nogc nothrow pure @system;
pragma(mangle, getOverloadMangling!2)
extern ptrdiff_t syscall_(ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t)
@nogc nothrow pure @system;
pragma(mangle, getOverloadMangling!3)
extern ptrdiff_t syscall_(ptrdiff_t,
ptrdiff_t,
ptrdiff_t,
ptrdiff_t,
ptrdiff_t,
ptrdiff_t,
ptrdiff_t) @nogc nothrow pure @system;

View File

@ -0,0 +1,76 @@
/* 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/. */
/*
* Copyright: Eugene Wissner 2018-2019.
* 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)
*/
module tanya.sys.posix.ioctl;
version (TanyaNative):
enum
{
SIOCADDRT = 0x890B, // Add routing table entry.
SIOCDELRT = 0x890C, // Delete routing table entry.
SIOCRTMSG = 0x890D, // Call to routing system.
SIOCGIFNAME = 0x8910, // Get iface name.
SIOCSIFLINK = 0x8911, // Set iface channel.
SIOCGIFCONF = 0x8912, // Get iface list.
SIOCGIFFLAGS = 0x8913, // Get flags.
SIOCSIFFLAGS = 0x8914, // Set flags.
SIOCGIFADDR = 0x8915, // Get PA address.
SIOCSIFADDR = 0x8916, // Set PA address.
SIOCGIFDSTADDR = 0x8917, // Get remote PA address.
SIOCSIFDSTADDR = 0x8918, // Set remote PA address.
SIOCGIFBRDADDR = 0x8919, // Get broadcast PA address.
SIOCSIFBRDADDR = 0x891a, // Set broadcast PA address.
SIOCGIFNETMASK = 0x891b, // Get network PA mask.
SIOCSIFNETMASK = 0x891c, // Set network PA mask.
SIOCGIFMETRIC = 0x891d, // Get metric.
SIOCSIFMETRIC = 0x891e, // Set metric.
SIOCGIFMEM = 0x891f, // Get memory address (BSD).
SIOCSIFMEM = 0x8920, // Set memory address (BSD).
SIOCGIFMTU = 0x8921, // Get MTU size.
SIOCSIFMTU = 0x8922, // Set MTU size.
SIOCSIFNAME = 0x8923, // Set interface name.
SIOCSIFHWADDR = 0x8924, // Set hardware address.
SIOCGIFENCAP = 0x8925, // Get/set encapsulations.
SIOCSIFENCAP = 0x8926,
SIOCGIFHWADDR = 0x8927, // Get hardware address.
SIOCGIFSLAVE = 0x8929, // Driver slaving support.
SIOCSIFSLAVE = 0x8930,
SIOCADDMULTI = 0x8931, // Multicast address lists.
SIOCDELMULTI = 0x8932,
SIOCGIFINDEX = 0x8933, // Name -> if_index mapping.
SIOGIFINDEX = SIOCGIFINDEX, // Misprint compatibility.
SIOCSIFPFLAGS = 0x8934, // Set/get extended flags set.
SIOCGIFPFLAGS = 0x8935,
SIOCDIFADDR = 0x8936, // Delete PA address.
SIOCSIFHWBROADCAST = 0x8937, // Set hardware broadcast address.
SIOCGIFCOUNT = 0x8938, // Get number of devices.
SIOCGIFBR = 0x8940, // Bridging support.
SIOCSIFBR = 0x8941, // Set bridging options.
SIOCGIFTXQLEN = 0x8942, // Get the tx queue length.
SIOCSIFTXQLEN = 0x8943, // Set the tx queue length.
SIOCDARP = 0x8953, // Delete ARP table entry.
SIOCGARP = 0x8954, // Get ARP table entry.
SIOCSARP = 0x8955, // Set ARP table entry.
SIOCDRARP = 0x8960, // Delete RARP table entry.
SIOCGRARP = 0x8961, // Get RARP table entry.
SIOCSRARP = 0x8962, // Set RARP table entry.
SIOCGIFMAP = 0x8970, // Get device parameters.
SIOCSIFMAP = 0x8971, // Set device parameters.
SIOCADDDLCI = 0x8980, // Create new DLCI device.
SIOCDELDLCI = 0x8981, // Delete DLCI device.
}

View File

@ -0,0 +1,29 @@
/* 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/. */
/*
* Copyright: Eugene Wissner 2018-2019.
* 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)
*/
module tanya.sys.posix.mman;
version (TanyaNative):
enum
{
PROT_EXEC = 0x4, // Page can be executed.
PROT_NONE = 0x0, // Page cannot be accessed.
PROT_READ = 0x1, // Page can be read.
PROT_WRITE = 0x2, // Page can be written.
}
enum
{
MAP_FIXED = 0x10, // Interpret addr exactly.
MAP_PRIVATE = 0x02, // Changes are private.
MAP_SHARED = 0x01, // Share changes.
MAP_ANONYMOUS = 0x20, // Don't use a file.
}

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/. */
/*
* Copyright: Eugene Wissner 2018-2019.
* 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)
*/
module tanya.sys.posix.net.if_;
version (TanyaNative):
enum size_t IF_NAMESIZE = 16;
struct ifreq
{
char[IF_NAMESIZE] ifr_name;
union
{
int ifr_ifindex;
}
}

View File

@ -0,0 +1,150 @@
/* 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/. */
/*
* Copyright: Eugene Wissner 2018-2019.
* 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)
*/
module tanya.sys.posix.socket;
version (TanyaNative):
/*
* Protocol families.
*/
enum
{
PF_UNSPEC = 0, // Unspecified.
PF_LOCAL = 1, // Local to host (pipes and file-domain).
PF_UNIX = PF_LOCAL, // POSIX name for PF_LOCAL.
PF_FILE = PF_LOCAL, // Another non-standard name for PF_LOCAL.
PF_INET = 2, // IP protocol family.
PF_AX25 = 3, // Amateur Radio AX.25.
PF_IPX = 4, // Novell Internet Protocol.
PF_APPLETALK = 5, // Appletalk DDP.
PF_NETROM = 6, // Amateur radio NetROM.
PF_BRIDGE = 7, // Multiprotocol bridge.
PF_ATMPVC = 8, // ATM PVCs.
PF_X25 = 9, // Reserved for X.25 project.
PF_INET6 = 10, // IP version 6.
PF_ROSE = 11, // Amateur Radio X.25 PLP.
PF_DECnet = 12, // Reserved for DECnet project.
PF_NETBEUI = 13, // Reserved for 802.2LLC project.
PF_SECURITY = 14, // Security callback pseudo AF.
PF_KEY = 15, // PF_KEY key management API.
PF_NETLINK = 16, // Kernel user interface device.
PF_ROUTE = PF_NETLINK, // Alias to emulate 4.4BSD.
PF_PACKET = 17, // Packet family.
PF_ASH = 18, // Ash.
PF_ECONET = 19, // Acorn Econet.
PF_ATMSVC = 20, // ATM SVCs.
PF_RDS = 21, // RDS sockets.
PF_SNA = 22, // Linux SNA Project.
PF_IRDA = 23, // IRDA sockets.
PF_PPPOX = 24, // PPPoX sockets.
PF_WANPIPE = 25, // Wanpipe API sockets.
PF_LLC = 26, // Linux LLC.
PF_IB = 27, // Native InfiniBand address.
PF_MPLS = 28, // MPLS.
PF_CAN = 29, // Controller Area Network.
PF_TIPC = 30, // TIPC sockets.
PF_BLUETOOTH = 31, // Bluetooth sockets.
PF_IUCV = 32, // IUCV sockets.
PF_RXRPC = 33, // RxRPC sockets.
PF_ISDN = 34, // mISDN sockets.
PF_PHONET = 35, // Phonet sockets.
PF_IEEE802154 = 36, // IEEE 802.15.4 sockets.
PF_CAIF = 37, // CAIF sockets.
PF_ALG = 38, // Algorithm sockets.
PF_NFC = 39, // NFC sockets.
PF_VSOCK = 40, // vSockets.
PF_MAX = 41, // For now.
}
/*
* Address families.
*/
enum
{
AF_UNSPEC = PF_UNSPEC,
AF_LOCAL = PF_LOCAL,
AF_UNIX = PF_UNIX,
AF_FILE = PF_FILE,
AF_INET = PF_INET,
AF_AX25 = PF_AX25,
AF_IPX = PF_IPX,
AF_APPLETALK = PF_APPLETALK,
AF_NETROM = PF_NETROM,
AF_BRIDGE = PF_BRIDGE,
AF_ATMPVC = PF_ATMPVC,
AF_X25 = PF_X25,
AF_INET6 = PF_INET6,
AF_ROSE = PF_ROSE,
AF_DECnet = PF_DECnet,
AF_NETBEUI = PF_NETBEUI,
AF_SECURITY = PF_SECURITY,
AF_KEY = PF_KEY,
AF_NETLINK = PF_NETLINK,
AF_ROUTE = PF_ROUTE,
AF_PACKET = PF_PACKET,
AF_ASH = PF_ASH,
AF_ECONET = PF_ECONET,
AF_ATMSVC = PF_ATMSVC,
AF_RDS = PF_RDS,
AF_SNA = PF_SNA,
AF_IRDA = PF_IRDA,
AF_PPPOX = PF_PPPOX,
AF_WANPIPE = PF_WANPIPE,
AF_LLC = PF_LLC,
AF_IB = PF_IB,
AF_MPLS = PF_MPLS,
AF_CAN = PF_CAN,
AF_TIPC = PF_TIPC,
AF_BLUETOOTH = PF_BLUETOOTH,
AF_IUCV = PF_IUCV,
AF_RXRPC = PF_RXRPC,
AF_ISDN = PF_ISDN,
AF_PHONET = PF_PHONET,
AF_IEEE802154 = PF_IEEE802154,
AF_CAIF = PF_CAIF,
AF_ALG = PF_ALG,
AF_NFC = PF_NFC,
AF_VSOCK = PF_VSOCK,
AF_MAX = PF_MAX,
}
/*
* Types of sockets.
*/
enum
{
// Sequenced, reliable, connection-based byte streams.
SOCK_STREAM = 1,
// Connectionless, unreliable datagrams of fixed maximum length.
SOCK_DGRAM = 2,
// Raw protocol interface.
SOCK_RAW = 3,
// Reliably-delivered messages.
SOCK_RDM = 4,
// Sequenced, reliable, connection-based, datagrams of fixed maximum
// length.
SOCK_SEQPACKET = 5,
// Datagram Congestion Control Protocol.
SOCK_DCCP = 6,
// Linux specific way of getting packets at the dev level. For writing rarp
// and other similar things on the user level.
SOCK_PACKET = 10,
}
/*
* Flags to be ORed into the type parameter of socket and socketpair and used
* for the flags parameter of paccept.
*/
enum
{
SOCK_CLOEXEC = 0x80000, // Atomically set close-on-exec flag for the new descriptor(s).
SOCK_NONBLOCK = 0x800, // Atomically mark descriptor(s) as non-blocking.
}

View File

@ -16,12 +16,10 @@
* defined here.
* Also aliases for specific types like $(D_PSYMBOL SOCKET) are defined here.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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/sys/windows/def.d,
* tanya/sys/windows/def.d)
*/
module tanya.sys.windows.def;
@ -30,6 +28,7 @@ version (Windows):
alias BYTE = ubyte;
alias TBYTE = wchar; // If Unicode, otherwise char.
alias CHAR = char; // Signed or unsigned char.
alias WCHAR = wchar;
alias TCHAR = wchar; // If Unicode, otherwise char.
alias SHORT = short;
alias USHORT = ushort;
@ -52,10 +51,14 @@ enum HANDLE INVALID_HANDLE_VALUE = cast(HANDLE) -1;
enum TRUE = 1;
enum FALSE = 0;
alias PSTR = CHAR*;
alias PWSTR = WCHAR*;
alias PTSTR = TCHAR*;
align(1) struct GUID
{
uint Data1;
ushort Data2;
ushort Data3;
char[8] Data4;
}
}

View File

@ -0,0 +1,28 @@
/* 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/. */
/**
* Copyright: Eugene Wissner 2018-2019.
* 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)
*/
module tanya.sys.windows.ifdef;
version (Windows):
import tanya.sys.windows.def;
union NET_LUID_LH
{
ulong Value;
ulong Info;
}
alias NET_LUID = NET_LUID_LH;
alias IF_LUID = NET_LUID_LH;
alias NET_IFINDEX = ULONG;
enum size_t IF_MAX_STRING_SIZE = 256;

View File

@ -0,0 +1,37 @@
/* 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/. */
/**
* Copyright: Eugene Wissner 2018-2019.
* 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)
*/
module tanya.sys.windows.iphlpapi;
version (Windows):
import tanya.sys.windows.def;
import tanya.sys.windows.ifdef;
extern(Windows)
DWORD ConvertInterfaceNameToLuidA(const(CHAR)* InterfaceName,
NET_LUID* InterfaceLuid)
@nogc nothrow @system;
extern(Windows)
DWORD ConvertInterfaceLuidToIndex(const(NET_LUID)* InterfaceLuid,
NET_IFINDEX* InterfaceIndex)
@nogc nothrow @system;
extern(Windows)
DWORD ConvertInterfaceIndexToLuid(NET_IFINDEX InterfaceIndex,
NET_LUID* InterfaceLuid)
@nogc nothrow @system;
extern(Windows)
DWORD ConvertInterfaceLuidToNameA(const(NET_LUID)* InterfaceLuid,
PSTR InterfaceName,
size_t Length)
@nogc nothrow @system;

View File

@ -3,18 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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/sys/windows/package.d,
* tanya/sys/windows/package.d)
*/
module tanya.sys.windows;
version (Windows):
public import tanya.sys.windows.def;
public import tanya.sys.windows.error;
public import tanya.sys.windows.ifdef;
public import tanya.sys.windows.iphlpapi;
public import tanya.sys.windows.winbase;
public import tanya.sys.windows.winsock2;
public import tanya.sys.windows.winsock2;

View File

@ -5,12 +5,10 @@
/**
* Definitions from winbase.h.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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/sys/windows/winbase.d,
* tanya/sys/windows/winbase.d)
*/
module tanya.sys.windows.winbase;
@ -52,4 +50,4 @@ extern(Windows)
BOOL GetOverlappedResult(HANDLE hFile,
OVERLAPPED* lpOverlapped,
DWORD* lpNumberOfBytesTransferred,
BOOL bWait) nothrow @system @nogc;
BOOL bWait) nothrow @system @nogc;

View File

@ -5,12 +5,10 @@
/**
* Definitions from winsock2.h, ws2def.h and MSWSock.h.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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/sys/windows/winsock2.d,
* tanya/sys/windows/winsock2.d)
*/
module tanya.sys.windows.winsock2;
@ -216,4 +214,4 @@ enum
SO_UPDATE_ACCEPT_CONTEXT = 0x700B,
SO_CONNECT_TIME = 0x700C,
SO_UPDATE_CONNECT_CONTEXT = 0x7010,
}
}

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"]
}

View File

@ -13,7 +13,7 @@
* The functions can cause segmentation fault if the module is compiled
* in production mode and the condition fails.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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)
@ -22,7 +22,7 @@
*/
module tanya.test.assertion;
import tanya.memory;
import tanya.memory.allocator;
import tanya.meta.trait;
/**

View File

@ -5,7 +5,7 @@
/**
* Test suite for $(D_KEYWORD unittest)-blocks.
*
* Copyright: Eugene Wissner 2017.
* Copyright: Eugene Wissner 2017-2019.
* 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,3 +15,4 @@
module tanya.test;
public import tanya.test.assertion;
public import tanya.test.stub;

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

@ -0,0 +1,373 @@
/* 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-2019.
* 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 (!empty)
{
static if (!infinite)
{
--this.length_;
}
}
static if (withLvalueElements)
{
ref E front() @nogc nothrow pure @safe
in (!empty)
{
return *this.element;
}
}
else
{
E front() @nogc nothrow pure @safe
in (!empty)
{
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 (!empty)
{
static if (!infinite)
{
--this.length_;
}
}
static if (withLvalueElements)
{
ref E back() @nogc nothrow pure @safe
in (!empty)
{
return *this.element;
}
}
else
{
E back() @nogc nothrow pure @safe
in (!empty)
{
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,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.comparison;
import tanya.algorithm.comparison;
import tanya.math;
import tanya.range;
@nogc nothrow pure @safe unittest
{
static assert(!is(typeof(min(1, 1UL))));
}
@nogc nothrow pure @safe unittest
{
assert(min(5, 3) == 3);
assert(min(4, 4) == 4);
assert(min(5.2, 3.0) == 3.0);
assert(min(5.2, double.nan) == 5.2);
assert(min(double.nan, 3.0) == 3.0);
assert(isNaN(min(double.nan, double.nan)));
}
@nogc nothrow pure @safe unittest
{
assert(min(cast(ubyte[]) []).empty);
}
@nogc nothrow pure @safe unittest
{
static assert(!is(typeof(max(1, 1UL))));
}
@nogc nothrow pure @safe unittest
{
assert(max(5, 3) == 5);
assert(max(4, 4) == 4);
assert(max(5.2, 3.0) == 5.2);
assert(max(5.2, double.nan) == 5.2);
assert(max(double.nan, 3.0) == 3.0);
assert(isNaN(max(double.nan, double.nan)));
}
@nogc nothrow pure @safe unittest
{
assert(max(cast(ubyte[]) []).empty);
}
// min/max compare const and mutable structs.
@nogc nothrow pure @safe unittest
{
static struct S
{
int s;
int opCmp(typeof(this) that) const @nogc nothrow pure @safe
{
return this.s - that.s;
}
}
{
const s1 = S(1);
assert(min(s1, S(2)).s == 1);
assert(max(s1, S(2)).s == 2);
}
{
auto s2 = S(2), s3 = S(3);
assert(min(s2, s3).s == 2);
assert(max(s2, s3).s == 3);
}
}
@nogc nothrow pure @safe unittest
{
static struct OpCmp(int value)
{
int opCmp(OpCmp) @nogc nothrow pure @safe
{
return value;
}
}
{
OpCmp!(-1)[1] range;
assert(compare(range[], range[]) < 0);
}
{
OpCmp!1[1] range;
assert(compare(range[], range[]) > 0);
}
{
OpCmp!0[1] range;
assert(compare(range[], range[]) == 0);
}
}

View File

@ -0,0 +1,127 @@
/* 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;
// length is unknown when taking from a range without length
@nogc nothrow pure @safe unittest
{
static struct R
{
mixin InputRangeStub;
}
auto actual = take(R(), 100);
static assert(!hasLength!(typeof(actual)));
}
// Takes minimum length if the range length > n
@nogc nothrow pure @safe unittest
{
auto range = take(cast(int[]) null, 8);
assert(range.length == 0);
}
@nogc nothrow pure @safe unittest
{
const int[9] range = [1, 2, 3, 4, 5, 6, 7, 8, 9];
{
auto slice = take(range[], 8)[1 .. 3];
assert(slice.length == 2);
assert(slice.front == 2);
assert(slice.back == 3);
}
{
auto slice = takeExactly(range[], 8)[1 .. 3];
assert(slice.length == 2);
assert(slice.front == 2);
assert(slice.back == 3);
}
}
// Elements are accessible in reverse order
@nogc nothrow pure @safe unittest
{
const int[3] given = [1, 2, 3];
auto actual = retro(given[]);
assert(actual.back == given[].front);
assert(actual[0] == 3);
assert(actual[2] == 1);
actual.popBack();
assert(actual.back == 2);
assert(actual[1] == 2);
// Check slicing.
auto slice = retro(given[])[1 .. $];
assert(slice.length == 2 && slice.front == 2 && slice.back == 1);
}
// Elements can be assigned
@nogc nothrow pure @safe unittest
{
int[4] given = [1, 2, 3, 4];
auto actual = retro(given[]);
actual.front = 5;
assert(given[].back == 5);
actual.back = 8;
assert(given[].front == 8);
actual[2] = 10;
assert(given[1] == 10);
}
// 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,128 @@
/* 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 tanya.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 (this.value == 0)
{
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);
}
@nogc nothrow pure @safe unittest
{
import tanya.algorithm.comparison : equal;
const int[5] expected = [1, 2, 3, 4, 5];
int[5] actual = [4, 5, 1, 2, 3];
rotate(actual[0 .. 2], actual[2 .. $]);
assert(equal(actual[], expected[]));
}
// Doesn't cause an infinite loop if back is shorter than the front
@nogc nothrow pure @safe unittest
{
import tanya.algorithm.comparison : equal;
const int[5] expected = [1, 2, 3, 4, 5];
int[5] actual = [3, 4, 5, 1, 2];
rotate(actual[0 .. 3], actual[3 .. $]);
assert(equal(actual[], expected[]));
}
// Doesn't call .front on an empty front
@nogc nothrow pure @safe unittest
{
import tanya.algorithm.comparison : equal;
const int[2] expected = [2, 8];
int[2] actual = expected;
rotate(actual[0 .. 0], actual[]);
assert(equal(actual[], expected[]));
}

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