535 Commits

Author SHA1 Message Date
d7dfa3f6f1 net.ip.Address6.toString() recommended notation
Fix #65.
2019-03-01 08:28:36 +01:00
8fd0452cd0 algorithm.iteration: Add singleton()
... iterating over a single value.
2019-02-25 09:27:03 +01:00
df99ea45f2 range.adapter: new arrayInserter 2019-02-24 13:14:30 +01:00
87ba58098e format.sformat: Support range-based toString() 2019-02-19 06:39:39 +01:00
5a134ce768 net.ip: Implement .toString() with output ranges 2019-02-16 08:37:45 +01:00
0835edce1d range.adapter: Add container-range adapters. Fix #67 2019-02-16 08:36:50 +01:00
a786bdbec5 Use word-wise hash instead of FNV-1a for arrays of word-aligned scalars
Also special case int-aligned scalars on 64-bit machines.
On a 64-bit machine hashing an array of pointers is now ~5.95x faster
with LDC2 and ~8.54x faster with DMD, and hashing an array of ints is
~3.34x faster with LDC2 and ~8.12x faster with DMD.
2019-02-12 10:34:18 -05:00
0bef2ef76d Add sformat() writing to an output range 2019-02-12 07:37:24 +01:00
1d3d750adb Update dmd to 2.084.1 2019-02-11 22:14:59 +01:00
0c8f1eb4ce Deprecate InputRange source for OutputRanges
An output range for E won't be automatically an output range for [E]
anymore. The same, an output range for [E] won't be automatically an
output range for E. Automatic E <-> [E] conversion seems to be a nice
feature at first glance, but it causes much ambiguity.

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

2) Here is an example from 2013:

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

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

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

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

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

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

Also adds "put" for the output ranges.

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

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

Unittests for "Instantiate".
2017-08-28 16:07:02 +02:00
4dbfbe9874 Add new metafunctions: Min, Max, ZipWith
Documentation follow
2017-08-27 15:32:05 +02:00
25d59ffdda Remove "static" prefix from metafunctions 2017-08-26 10:37:22 +02:00
2c064eb05b Add hasElaborate traits 2017-08-25 14:50:15 +02:00
c9a4a2f651 Add "native" configuration and TanyaPhobos version 2017-08-25 00:29:43 +02:00
0e99effaeb net.inet: Remove htonl/htons based unit tests 2017-08-24 07:45:16 +02:00
0f1e53b4b9 format.conv: Replace loop with copy() 2017-08-22 12:47:13 +02:00
666d59c231 Add traits for checking if class, iface, struct
They are useful for compile-time algorithms like Filter, StaticMap and
so on.
2017-08-22 11:12:41 +02:00
ce90b4865b Make front and popFront for arrays public
This commit adds tanya.range.array module which contains functions to make
the arrays act as ranges. These functions don't do any auto-decoding for
strings.
2017-08-21 06:49:02 +02:00
beb5d6963b Complete tanya.meta.metafunction 2017-08-20 12:29:48 +02:00
a188f8b6e2 Rename traits module to trait 2017-08-19 11:28:08 +02:00
9355c54163 Add metafunctions 2017-08-18 23:38:41 +02:00
e8dd6e3217 Add more traits 2017-08-16 06:45:15 +02:00
94a7fdbb91 Update latest DMD to 2.075.1 2017-08-15 01:18:21 +02:00
afd3c42c5f Add meta.traits module 2017-08-14 14:21:10 +02:00
1d91bb4df9 Add templates to meta.transform 2017-08-14 14:13:43 +02:00
a5026e48d8 Add meta.transform package
Templates in this module applied to a type produce a transformed type.
2017-08-13 19:12:46 +02:00
64f2295d1a Fix #276
Add link to the source file for each module.
2017-08-12 17:01:51 +02:00
dea0eb9a37 Add function for comparing memory regions
memory.op.cmp.
2017-08-11 22:15:01 +02:00
7c2abadb90 Add memory.op.copyBackward
Added function that can copy memory chunks that can overlap.
2017-08-09 07:01:57 +02:00
e6b28468ca Fix typo in the README, remove dmd 2.072 support 2017-08-08 05:59:04 +02:00
2934bb16d7 Rename memory.op.zero into fill
- Rename memory.op.zero to fill, which accepts one template parameter: one
byte to fill the memory with.
- Fix bug on x86_64: RAX (register keeping the value to fill with) isn't set if
the pointer was already aligned.
2017-08-06 06:22:28 +02:00
ed92e3993e Add fast function to zero memory 2017-08-02 06:41:54 +02:00
1a4d1238a1 Remove dmd 2.071.2 support 2017-08-01 05:17:57 +02:00
04864559e2 Respect how Windows passes arrays on x86_64
tanya.memory.arch.x86_64:
Linux passes the array length and the data pointer in separate registers.
Windows passes a pointer to the whole array instead (pointer to the
array length practically).
2017-07-31 04:23:21 +02:00
40e43c1465 Add memory.op.copy 2017-07-30 00:08:41 +02:00
5d145f524c Add fast memory copy function for x86-64 2017-07-29 10:08:44 +02:00
51ade45108 Add internal routing to convert a number to string
Add internal routing to write a number to a char buffer.
2017-07-28 09:08:58 +02:00
3afb40e09e format.conv: Convert string to a boolean 2017-07-27 08:48:44 +02:00
a9cc135318 format.conv: Add conversion from bool to String 2017-07-26 06:49:33 +02:00
1389b03842 memory: Fix parameter name in the documentation
Size parameter for "make" was renamed into n, but the function
description wasn't fixed:
  size => n

This commit also removes some redundant variables in "make".
2017-07-25 07:40:14 +02:00
a37c9b162e container.Set: Reduce duplicated code, add tests 2017-07-22 07:40:58 +02:00
cde492c279 Add dmd 2.075.0 support 2017-07-21 05:44:45 +02:00
922c8bf7a3 Fix assigning a ByCodeUnit to the String slice
std.algorithm.mutation copy is unable to copy a char range into a char array slice.
2017-07-19 07:58:48 +02:00
a0a28c76f7 Fix CONTRIBUTING.md typos 2017-07-19 07:58:20 +02:00
a1f4d2bc1c If scheme is invalid, parse everything as path 2017-07-18 23:01:57 +02:00
e5fb95ceb0 Fix #254
network.url Range violation.

Add a check after parsing "scheme://"  whether only the scheme is available.
2017-07-17 04:57:33 +02:00
9ef5986288 Add some style guidelines for contributors 2017-07-16 18:56:48 +02:00
42146c5e8a Fix #259
Get rid of std.experimental.
2017-07-15 22:25:29 +02:00
e6b91f70cb Add style checking
A lot of tests are disabled. They should be enabled successively.
2017-07-14 00:05:13 +02:00
657f4a60d5 Fix #246
Make allocators pure.
* Methods allocating/deallocating memory are pure.
* Allocator.instance is pure (once initialized, it always returns
  the same instance).
* defaultAllocator getter property is pure (should be set at the
  beginning, and always return the same instance after that).
2017-07-13 16:01:21 +02:00
839c740cb1 Fix mmap flags on linux 2017-07-12 10:04:48 +02:00
2bd612fd19 Make MmapPool allocations pure 2017-07-12 09:30:07 +02:00
fc53779d3f Fix #245
* Remove postcondition for functions calculating alignment
* Put MmapPool invariant into version (none) block
* Check that alignment doesn't overflow
2017-07-11 10:27:24 +02:00
7bdc778390 Fix inserting 3 byte wchar into String
* Fix inserting 3 byte wchar into String
* Improve documentation
2017-07-09 15:16:06 +02:00
97358ebc6c Ignore tanya-test-library.core (FreeBSD) 2017-07-08 15:54:47 +02:00
4834b36271 Finish DList implementation. Fixes #209
* removeBack
* insertAfter
* Diverse fixes of insertion logic
* Internal moveFront and moveBack functions
* Internal makeList function
2017-07-08 15:51:17 +02:00
53df12897b Add missing methods to DList. Issue #209 2017-07-08 13:44:57 +02:00
4ac890d7d3 Fix #260
DList invariant fails.
2017-07-08 05:41:04 +02:00
b79657f0d2 Fix 232 2017-07-06 08:35:16 +02:00
9429e7bb14 Refer to net instead of network package in README 2017-07-05 23:11:54 +02:00
4fd37e84f8 Fix #232 for Array
Because const is transitive, if we create a range as Range!(const E)
there is no way to get the original type from inside of the range. So if
E is int*, the original type of const(E) could be const(int)* or int*.
Unqual!(const(int*)) returns const(int)*. So pass the whole container as
template parameter. It is a breaking change but since we have Range and
ConstRange aliases now, the usage should be fine.
2017-07-04 07:24:29 +02:00
e46e45ad5a Remove previously deprecated modules
* tanya.network.uri
* tanya.network.inet
* tanya.memory.types
2017-06-30 04:19:20 +02:00
e79c75df81 Fix typo in README, add CONTRIBUTING.md link 2017-06-29 11:06:40 +02:00
a6dfb3a19e Fix DList.opAssign not changing tail 2017-06-28 08:12:58 +02:00
2af0db04bd Move network.url to net.uri 2017-06-27 13:23:17 +02:00
2c9867c577 Fix generating async docs for different OS 2017-06-25 09:46:02 +02:00
47b394d8c3 Add module documentation. Fix #248 2017-06-24 09:08:19 +02:00
ede0107fd7 Fix #247
Assigning RefCounted to RefCounted fails at compile time.

https://issues.caraus.io/issues/247
2017-06-24 02:28:17 +02:00
7d5dda1cba Add Unique.isInitialized and Unique.release 2017-06-24 00:51:16 +02:00
e5f83c22fa Add support for enums to format.conv.to. Fix #240 2017-06-23 02:58:46 +02:00
a4de1cc754 toStringz returns a pointer 2017-06-22 11:48:58 +02:00
8d3cdb8862 Add "Basic usage" section. Fix #238 2017-06-22 02:56:18 +02:00
ba1bd35d4a Finish CONTRIBUTING.md 2017-06-21 15:05:39 +02:00
dfacabd88b format.conv.to: Convert to/from boolean 2017-06-20 07:07:58 +02:00
aa306d9050 Add contributing information
Only the section with ways to get involved.
2017-06-19 09:13:02 +02:00
10019d7df9 Add No Code of Conduct 2017-06-19 06:11:32 +02:00
ae36296ca6 Add tanya.format.conv.to
Function that converts between different types.
This first commit adds only conversion between integral types.
2017-06-18 18:05:50 +02:00
56406fb593 Mark Entropy class as nogc, add linux 64bit unittest 2017-06-17 08:58:44 +02:00
ec9b2db4b9 Add os package# 2017-06-16 21:41:23 +02:00
f5d0c2af8f Revert "Add unittest for Linux random generator"
Doesn't work on 32-bit.
This reverts commit c62dc4063e.
2017-06-15 11:21:56 +02:00
c62dc4063e Add unittest for Linux random generator 2017-06-15 11:19:50 +02:00
3789853d98 Fix one Mallocator test
Test that if the reallocation fails, the pointer doesn't change.
2017-06-15 10:37:50 +02:00
f0d016bcde Replace in and immutable with const in allocators 2017-06-15 10:27:12 +02:00
70e96c62b3 Make Unique.get and RefCounted.get return inout
Also revert the renaming of Scoped to Unique. And rename the whole
module to memory.smartref.
2017-06-14 22:11:57 +02:00
b723d763c8 Test x86-64 on Windows 2017-06-13 12:17:14 +02:00
508297f36f Generate coverage for x86 aswell 2017-06-13 10:45:15 +02:00
4b0134713c Move new network modules into tanya.net package
tanya.net will combine tanya.async and tanya.network and provide one API
for blocking and non-blocking socket programming.
2017-06-13 10:42:35 +02:00
5b90286b70 Add x86 to tests 2017-06-12 19:02:47 +02:00
8443f1b385 Make test functions nogc 2017-06-11 09:45:33 +02:00
c9050c1a8e Rename Scoped to Unique. Improve unit tests
Renaming to avoid confusing with Phobos Scoped.
2017-06-11 09:41:18 +02:00
bdf87570e2 Add basic unit tests for the event loop 2017-06-11 09:15:10 +02:00
faa44b6704 Remove deprecated tanya.container.vector 2017-06-09 19:27:54 +02:00
278e851414 Rename String.toString to String.get()
Last changed it only by the Range and forgotten about the string itself.
2017-06-08 07:59:16 +02:00
6f549df243 Update README description 2017-06-07 08:04:50 +02:00
4633bcc680 Set: Fix comparing with removed elements 2017-06-07 07:57:22 +02:00
dc39efd316 Add some unit tests for InternetAddress 2017-06-03 15:18:53 +02:00
260937e4fb Put socket overlapped I/O docs into a D_Ddoc block 2017-06-03 13:20:32 +02:00
e17fff2881 Update 2.074 compiler 2017-06-02 22:01:13 +02:00
bc32511254 Fix template parameters for Set 2017-06-01 22:36:38 +02:00
f30972f948 Add basic constructors and opAssign to Set 2017-06-01 06:26:06 +02:00
ea33ca62c8 Implement lookups in the Set 2017-05-31 10:29:07 +02:00
0f365758e1 Add optional fieldnames for Pair 2017-05-30 20:20:20 +02:00
2815b53a88 Implement Set Range 2017-05-30 15:52:18 +02:00
6c0588164a Rename String.toString back to get()
Since it is expected that the return type of toString() is
immutable(char)[] and not char[] or const(char)[].
2017-05-29 11:41:49 +02:00
8ee1d647ce Close issue 212
Introduce Range and constRange aliases for containers.
2017-05-29 11:26:39 +02:00
25791775e6 Add information about the Set to README 2017-05-29 10:58:37 +02:00
f013e2f1f4 Implement a Set container first 2017-05-29 10:50:01 +02:00
ac3935d71b Merge branch 'master' into horton-table 2017-05-28 10:15:02 +02:00
b1c217e272 Fix kqueue to work with SocketType 2017-05-25 22:21:45 +02:00
d007aaa310 Rename socket_t to SocketType 2017-05-25 21:59:40 +02:00
8aaf9e14be Add HashTable struct 2017-05-23 22:17:35 +02:00
ae3e6b46f6 Import std.algorithm.comparison for network.socket on Windows 2017-05-21 10:25:54 +02:00
8687df1fbb Define AddressFamily in network.socket 2017-05-21 10:20:57 +02:00
ba0aff6737 Add tanya.typecons.Pair 2017-05-19 21:15:56 +02:00
a648e2120a Fix parameter count for docs in container.string 2017-05-19 20:01:04 +02:00
bc61809050 Implement DList.insertBack 2017-05-16 13:16:18 +02:00
8c42cbfd63 Rename Vector to Array
For consistency with Phobos.
2017-05-16 12:12:57 +02:00
58664570f9 Add new branch, add DList to package description 2017-05-15 20:09:32 +02:00
decb82f437 Remove crypto.mac for now
It wasn't released yet and needs some work.
2017-05-15 19:57:36 +02:00
357c7e279d Add doubly-linked list
DList is an adjusted copy of SList. Missing:
* insertBack
* insertAfter
* removeBack
2017-05-15 19:50:20 +02:00
32e19c8b58 Rename String.get into toString. Add String.toStringz 2017-05-14 11:56:57 +02:00
f5c6c5b483 Add Payload template for memory.types 2017-05-13 08:43:49 +02:00
ba2d086fb8 Add memory.types.Scoped 2017-05-13 08:27:51 +02:00
7a0241b484 Fix unittest text for strings 2017-05-12 22:42:43 +02:00
36dad80e18 Use char ranges to avoid compilation errors on elder compilers 2017-05-12 21:46:48 +02:00
29d883150e Fix unittests on 2.072 and 2.071 2017-05-12 21:10:22 +02:00
e2bed0cfcb Replace tabs with spaces in mmappool and buffer 2017-05-12 21:02:24 +02:00
38afeac071 Insert String.insertFront and String.insertBack 2017-05-12 20:55:42 +02:00
001c7c3e33 Replace immutable with const in Vector 2017-05-12 20:35:16 +02:00
d4ab339feb Add String.remove 2017-05-12 20:23:16 +02:00
8477312769 Add editorconfig 2017-05-11 13:57:24 +02:00
67f90e137d Add codecov badge 2017-05-11 13:15:04 +02:00
f264fd5597 Generate unittest coverage information 2017-05-11 13:11:12 +02:00
9e75620f1b Fix appveyor branch badges 2017-05-11 07:05:13 +02:00
45825946c0 Appveyor (#10)
* Add appveyor.yml

* Try major VC version

* Switch to VC 2015

* Try new version

* Try enterprise

* Try another path

* Change VC template

* Set arch

* Set LINKCMD64

* Fix quotes

* Update LINKCMD64

* remove dir

* Update arch

* Fix syntax

* Set arch to x64

* Remove extra dub downloading

* Remove dub version

* Download dub for 2.071.2

* Use DVersion

* Fix nul in powershell

* Put quotes to commands

* Add badges
2017-05-11 06:26:59 +02:00
8afb552d59 mp.Integer: add two's complement constructor 2017-05-10 19:27:25 +02:00
e4091669f8 Add information about io branch 2017-05-10 13:18:58 +02:00
1cb9349226 math.mp.Integer.toVector return two's complement 2017-05-09 06:27:30 +02:00
06620dc5df math.mp.Integer: Return two's complement length 2017-05-08 21:09:52 +02:00
708d95db49 Remove utf8string branch 2017-05-06 11:55:20 +02:00
85d9361bfb Fix fill with char on older compilers 2017-05-05 07:03:16 +02:00
a6a6f496eb Implement string slice assignments 2017-05-04 23:17:50 +02:00
db12f03264 Merge branch 'master' into utf8string 2017-05-03 19:15:13 +02:00
231aedb8ad Add HMAC 2017-05-03 19:05:23 +02:00
c3b63ee40d Merge branch 'master' into utf8string 2017-05-02 10:59:00 +02:00
6f405c5e08 Make Vector's opSliceAssign accept only own ranges
Vector.opSliceAssign and Vector.opIndexAssign should accept only vector
ranges. For assigning other ranges, std.algorithm.mutation.copy and
std.algorithm.mutation.fill should be used.
2017-05-02 10:56:32 +02:00
16cf8478cf Add ByCodePoint 2017-05-01 20:17:37 +02:00
8915a0c7a7 Implement opCmp and opEquals for the String 2017-05-01 18:43:12 +02:00
e5c7edb72c Implement String opAssign 2017-05-01 12:58:37 +02:00
64e0d666ed Merge branch 'master' of github.com:caraus-ecms/tanya into utf8string 2017-05-01 09:59:29 +02:00
f2aac680c5 Fix container ctors and opAssign ref parameters
Container constructors and opAssign should accept any ref container and
not only const, otherwise the source container will be copied because
the constructor/opAssign without ref would be a better match.
2017-05-01 09:48:12 +02:00
65c3ca14ec Integer storage optimization 2017-04-30 16:07:44 +02:00
4fa47153ba Make Integer representation little endian 2017-04-25 19:50:06 +02:00
d629525a4b Make String to be a char Slice alias 2017-04-21 14:03:20 +02:00
33d321f0d7 Merge branch 'master' into utf8string 2017-04-20 17:32:59 +02:00
3d64d59ba9 Merge branch 'master' of github.com:caraus-ecms/tanya 2017-04-20 17:32:29 +02:00
4635835a99 Rename Vector range to Slice 2017-04-20 17:32:16 +02:00
8725ec5f20 Make Integer representation little endian 2017-04-19 13:49:44 +02:00
9a4c8cea06 Merge branch 'master' into utf8string 2017-04-16 20:52:40 +02:00
eb360bda38 Add unittest to check RefCounted calles struct destructors 2017-04-16 20:52:24 +02:00
4b1cd2cbfd Merge branch 'master' into utf8string 2017-04-16 20:15:11 +02:00
628153e2e8 Make RefCounted work with dynamic arrays 2017-04-16 20:14:04 +02:00
7aa9ac9f4a Add internal finalize method for finalizing an object without deallocating 2017-04-16 20:13:20 +02:00
cd944a61b7 Merge remote-tracking branch 'origin/master' into utf8string 2017-04-13 16:03:00 +02:00
8156d0fe3a Add support for dmd 2.074.0, remove 2.070.2 2017-04-13 16:02:18 +02:00
47ef787353 Add missing constructors to the String 2017-04-10 08:10:08 +02:00
6436ad49df Add ByteRange to the String 2017-04-08 17:44:08 +02:00
e1964e47a5 Merge branch 'master' into utf8string 2017-04-08 08:44:21 +02:00
6e2ce5d686 Remove opApply from containers
opApply requires additional overloads for the const containers (with a
const delegate). If using a templated opApply foreach cannot infer the
types for the variables. foreach with one argument still works
(opIndex() is used), for more complex cases slicing should be used.
2017-04-07 16:00:50 +02:00
ba6bf554fb Make SList range public 2017-04-07 15:17:14 +02:00
b1d2b9bd9e Fix Vector.insertAfter/Before an empty range 2017-04-04 15:11:14 +02:00
9b953198fa Fix network.inet release build 2017-04-04 08:36:42 +02:00
bc2a6d2703 Swap toHostOrder template parameters 2017-04-03 15:32:15 +02:00
b458250ad7 Make NetworkOrder work with 8-byte integers 2017-04-02 20:55:22 +02:00
b08d5e5d83 Add tanya.network.inet.toHostOrder
The function reverts NetworkOrder.
2017-04-02 11:16:08 +02:00
445b872e91 Add tanya.network.inet.NetworkOrder
NetworkOrder converts an integral type into a bidirectional range with
big-endian byte order.
2017-04-02 09:29:54 +02:00
5e16fe98d6 Add tanya.network package file 2017-04-01 09:53:59 +02:00
647cfe03c2 Update latest supported compiler 2017-03-29 17:23:10 +02:00
4cd6126d6b Fix SList documentation for insertFront and insertBefore 2017-03-29 17:22:25 +02:00
b870179a35 Move bitvector to another branch till it is finished 2017-03-29 11:17:03 +02:00
aabb4fb534 Add SList.opAssign 2017-03-29 10:35:45 +02:00
4d8b95812e Implement opAssign for the Vector 2017-03-28 20:42:42 +02:00
e921413249 Merge branch 'master' of github.com:caraus-ecms/tanya 2017-03-24 20:54:47 +01:00
49cae88645 Add insertBefore and remove to SList 2017-03-24 20:54:28 +01:00
402fdfae89 math.mp: Fix initialization issues after resizing 2017-03-23 15:36:17 +01:00
7892c1a930 Remove Init template parameter from memory.resize() 2017-03-22 08:51:00 +01:00
b90517580e Merge math.mp.Integer changes from the crypto branch 2017-03-21 19:25:12 +01:00
85380ac3fc Remove makeArray import 2017-03-19 06:54:59 +01:00
b90c56395c Remove resizeArray alias 2017-03-19 06:10:27 +01:00
d0ada39fa7 Add Mallocator as an alternative allocator 2017-03-18 08:07:01 +01:00
f4145abfd1 Add SList constructors 2017-03-09 06:07:23 +01:00
093d499729 Fix element order inserted from a range into list 2017-03-08 07:12:23 +01:00
f90a03501b Move BitVector from the crypto branch 2017-03-02 11:27:26 +01:00
c6a99b114e SList.insertFront for ranges 2017-03-01 19:23:54 +01:00
43319e4e3a Initialization from a UTF-16 string 2017-02-27 11:27:24 +01:00
33dbf042c2 Add dchar constructor 2017-02-26 22:40:27 +01:00
3c23aca6a6 Improve Vector module and reserve documentation 2017-02-20 12:03:49 +01:00
885fca9b5e Add String.reserve and shrink 2017-02-20 12:01:15 +01:00
074d027629 Merge branch 'master' into utf8string 2017-02-20 08:02:01 +01:00
70632d975d Add documentation link 2017-02-18 16:35:06 +01:00
d210a39249 Implement IOCPTransport.close and isClosing 2017-02-18 14:10:54 +01:00
e86ff63f91 Add DuplexTransport.close for the selector transport 2017-02-12 18:51:00 +01:00
3454a1965a Move all IOWatcher members to the transports 2017-02-11 19:47:57 +01:00
c41fa2e98f Adjust kqueue build 2017-02-10 23:01:33 +01:00
a012ca4003 Remove StreamTransport interface
Implement DuplexTransport and SocketTransport separately.
2017-02-10 22:30:12 +01:00
f4b90d8b51 Add string skeleton 2017-02-10 19:22:46 +01:00
b74e5aa4ee Make event loop implementations final 2017-02-10 19:19:37 +01:00
44ac15ab78 Move the bug tracker to Redmine 2017-02-10 17:28:55 +01:00
b1b652b943 Fix Kqueue buil with the new watcher-transport 2017-02-09 21:40:52 +01:00
530a482402 Inherit IOCPTransport from IOWatcher 2017-02-09 21:40:52 +01:00
f9023cf0ab Let Transport extend IOWatcher 2017-02-09 21:40:52 +01:00
0e91ea6786 Pass client socket directly to the IOWatcher 2017-02-09 21:40:52 +01:00
63c6226a2a Implement protocol property for IOCPTransport 2017-02-08 21:21:12 +01:00
48a49c2a2d Add protocol property to the transport
Transport should be protocol aware because it should be possible to
switch the protocol if the operation is supported, for example for upgrading
HTTP to web sockets or HTTP 1.1 to HTTP/2.
2017-02-08 20:04:05 +01:00
43c28b749d Rename async.loop.Loop.done_ to done
Since there is no property with a conflicting name.
2017-02-04 14:55:52 +01:00
241767df13 Move DefaultAllocator mixin to tanya.memory
Since it depends on defaultAllocator property defined in the module.
2017-02-03 13:07:40 +01:00
b2baba9237 SList: Add length and opEquals 2017-01-25 19:41:05 +01:00
3e36ec0984 Add support for dmd 2.070.2 2017-01-25 07:24:19 +01:00
5be89e4858 Add support for dmd 2.073.0 2017-01-24 16:15:14 +01:00
a48d9cb739 Add range support for SList 2017-01-24 08:20:07 +01:00
a7206cbd02 Fix #4 2017-01-22 10:48:34 +01:00
1450a6adfe Vector.insertBack: Accept by value and by ref 2017-01-20 05:40:28 +01:00
5fa9bd7b49 Rename Allocator.expand to reallocateInPlace
Rename and extend Allocator.expand to reallocateInPlace. The problem is
that realloc for example doesn't guarante that the shrinking of the
memory block won't cause address change. So not only expanding should
have "in place" version, but the shrinking as well.
2017-01-18 09:33:39 +01:00
c7eb233fc7 Make passed length parameter const 2017-01-16 10:56:45 +01:00
20c8b659d1 Remove mutation methods from vector range
in favor of std.algorithm.mutation.
2017-01-16 09:02:00 +01:00
4ea9c2b740 Vector: Reuse available methods 2017-01-15 08:38:19 +01:00
48205b2fc9 MmapPool: Add invariant
Add invariant to ensure blocks are linked correctly since this error
appeared several times.
2017-01-14 21:48:21 +01:00
f5fe7bec4a Queue optimization. Fix #5 2017-01-14 21:27:07 +01:00
c567b88d5d MmapPool: Fix expand block moving.
D dereferences the pointer wrong because of missing difference between .
and -> operators, if trying to write a block over another
block. So use memmove first to move the memory and then update the fields
that should be changed (only size).
2017-01-14 20:39:33 +01:00
fe884541fc Rename Vector.data to Vector.get 2017-01-13 15:23:42 +01:00
8973bdb2af Fix if EPOLLIN and EPOLLOUT come together 2017-01-13 10:20:11 +01:00
4c4e65b373 MmapPool: (p[] is null) != (p[].ptr is null) 2017-01-12 19:47:07 +01:00
7bed7f039f Remove default parameter value from Protocol.disconnected 2017-01-12 18:07:39 +01:00
8ddea0aa46 Loop.maxEvents is const, not inout const 2017-01-12 10:43:02 +01:00
cb6cc65113 async: Switch to the internal use of the vector instead of built-in arrays 2017-01-12 10:17:12 +01:00
4de42ca227 Use only one queue for the async events 2017-01-12 09:09:33 +01:00
101 changed files with 39078 additions and 10768 deletions

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

11
.gitignore vendored
View File

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

View File

@ -1,17 +1,62 @@
sudo: false sudo: false
os: os:
- linux - linux
- osx
language: d language: d
d: d:
- dmd-2.072.2 - dmd-2.084.1
- dmd-2.071.2 - dmd-2.083.1
- dmd-2.082.1
env: env:
matrix: global:
- LATEST=2.084.1
matrix:
- ARCH=x86_64 - ARCH=x86_64
- ARCH=x86
matrix:
include:
- name: D-Scanner
d: dmd-$LATEST
env: DSCANNER=0.6.0
os: linux
- name: DDoc
d: dmd-$LATEST
env: DDOC=true
os: linux
allow_failures:
- name: D-Scanner
d: dmd-$LATEST
env: DSCANNER=0.6.0
os: linux
addons:
apt:
packages:
- gcc-multilib
before_script:
- if [ "`$DC --version | head -n 1 | grep v$LATEST`" ] &&
[ -z "$DSCANNER$DDOC" ]; then
export UNITTEST="unittest-cov";
fi
script: script:
- dub test --arch=$ARCH - if [ -n "$DDOC" ]; then
dub build -b ddox --compiler=$DC;
elif [ -z "$DSCANNER" ]; then
dub test -b ${UNITTEST:-unittest} --arch=$ARCH --compiler=$DC;
else
dub fetch dscanner --version=$DSCANNER;
FILES=$(find source -type f);
dub run dscanner -- --styleCheck $FILES;
fi
after_success:
- test "$UNITTEST" && bash <(curl -s https://codecov.io/bash)

5
CODE_OF_CONDUCT.md Normal file
View File

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

107
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,107 @@
# Contributing
Tanya is a project in active development, therefore any help is appreciated. Thank you for considering contributing
to it, feel welcome.
These guidelines describe ways to get started.
## Ways to get involved
* **Reporting a problem**: [Report](https://github.com/caraus-ecms/tanya/issues) bugs and usage problems you
encounter.
* **Fixing issues**: [The bug tracker](https://github.com/caraus-ecms/tanya/issues) contains a list of issues you
can work on.
* **Documentation**: You can improve API documentation by correcting grammar errors, completing existing texts and
writing new ones, or providing usage examples.
* **Testing**: Test coverage is important for a library. Writing tests is not only helpful, but is also a great way
to get a feel for how tanya works.
* **Adding new features**: Tanya is a growing library. If you think some feature is missing, you can suggest
and implement this.
## Opening an issue
If you have found a bug, an error, have some question, or suggestion,
[Open an issue](https://github.com/caraus-ecms/tanya/issues). I'll try to answer as soon as I can. There is also a
list of open issues that mirror the current development process and progress. If you're looking for a challenge, just
pick an issue you are interested in and start working on it. Fill free to comment on the issue to get more
information.
You can also look at the [milestones](https://github.com/Dlackware/gnome/milestones) to see what is planned for a
specific release.
## Contribution process
### Creating a pull request
I accept GitHub pull requests. Creating a pull request is like sending a patch with the suggested change.
First you have to [fork](https://guides.github.com/activities/forking/) the repository. Clone your fork locally
with `git clone` and create a new branch where you want to work. For example:
```shell
git checkout -b bugfix-x
```
Commit your changes to your fork:
```shell
git commit -m "Fix X"
git push -u origin bugfix-x
```
After that if you visit your fork on GitHub, GitHub will suggest to create pull request. Just follow the steps
described on GitHub to finish the process. See
[Using Pull Requests](https://help.github.com/articles/about-pull-requests/) for more information.
Please ensure that your fork is even with the upstream (original) repository. If not, you have to rebase your branch
on upstream/master before submitting the pull request. See [Syncing a fork](https://help.github.com/articles/syncing-a-fork/) for a
step-by-step guide.
### Fixing a bug
Add a unit test that demonstrates the bug along with a short description or link to the original bug.
### Adding new features
* Use Ddoc to document the feature.
* Add some unit tests to prevent bugs.
* [Documented D unit tests](https://dlang.org/spec/ddoc.html#using_ddoc_to_generate_examples) go into the documentation and can be used as an usage
example. These tests should be readable and not complicated since they demonstrate how the feature is supposed to work.
* More advanced tests should be put into a separate not documented unittest block.
### Writing unit tests
```d
///
unittest
{
// A documented unit test has three slashes in front of it.
}
// Issue ##: https://github.com/caraus-ecms/tanya/issues/##.
unittest
{
// Not documented unit test may still have a description.
}
```
### Style guide
Make sure your changes follow [The D Style](https://dlang.org/dstyle.html) (including
[Additional Requirements for Phobos](https://dlang.org/dstyle.html#phobos)).
You can also use [dscanner](https://github.com/dlang-community/D-Scanner) to test the new code against the
most guidlines. The root of this repository contains
[dscanner.ini](https://github.com/caraus-ecms/tanya/blob/master/dscanner.ini), configuration file with settings for an
automatic style check. Just go to the top-level directory and issue (this assumes `dscanner` is installed in your
system):
```shell
dscanner --styleCheck source
```
## Questions and suggestions
* [Open an issue](https://github.com/caraus-ecms/tanya/issues)
* [Send an email](mailto:info@caraus.de)

221
README.md
View File

@ -1,6 +1,8 @@
# Tanya # 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) [![Dub version](https://img.shields.io/dub/v/tanya.svg)](https://code.dlang.org/packages/tanya)
[![Dub downloads](https://img.shields.io/dub/dt/tanya.svg)](https://code.dlang.org/packages/tanya) [![Dub downloads](https://img.shields.io/dub/dt/tanya.svg)](https://code.dlang.org/packages/tanya)
[![License](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE) [![License](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
@ -10,60 +12,207 @@ Tanya is a general purpose library for D programming language.
Its aim is to simplify the manual memory management in D and to provide a Its aim is to simplify the manual memory management in D and to provide a
guarantee with @nogc attribute that there are no hidden allocations on the guarantee with @nogc attribute that there are no hidden allocations on the
Garbage Collector heap. Everything in the library is usable in @nogc code. Garbage Collector heap. Everything in the library is usable in @nogc code.
Tanya extends Phobos functionality and provides alternative implementations for Tanya provides data structures and utilities to facilitate painless systems
data structures and utilities that depend on the Garbage Collector in Phobos. programming in D.
* [API Documentation](https://docs.caraus.io/tanya)
* [Contribution guidelines](CONTRIBUTING.md)
## Overview ## Overview
Tanya consists of the following packages: Tanya consists of the following packages and (top-level) modules:
* `algorithm`: Collection of generic algorithms.
* `async`: Event loop (epoll, kqueue and IOCP). * `async`: Event loop (epoll, kqueue and IOCP).
* `container`: Queue, Vector, Singly linked list, buffers. * `bitmanip`: Bit manipulation.
* `crypto`: Work in progress TLS implementation. * `container`: Queue, Array, Singly and doubly linked lists, Buffers, UTF-8
* `math`: Multiple precision integer and a set of functions. string, Set, Hash table.
* `memory`: Tools for manual memory management (allocator, reference counting, * `conv`: This module provides functions for converting between different
helper functions). types.
* `network`: URL-Parsing, sockets. * `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
type information at compile-time, to transform from one type to another. It has
also different algorithms for iterating, searching and modifying template
arguments.
* `net`: URL-Parsing, network programming.
* `network`: Socket implementation. `network` is currently under rework.
After finishing the new socket implementation will land in the `net` package and
`network` will be deprecated.
* `os`: Platform-independent interfaces to operating system functionality.
* `range`: Generic functions and templates for D ranges.
* `test`: Test suite for unittest-blocks.
* `typecons`: Templates that allow to build new types based on the available
ones.
## NogcD
To achieve programming without the Garbage Collection tanya uses a subset of D:
NogcD.
### Allocators
Memory management is done with allocators. Instead of using `new` to create an
instance of a class, an allocator is used:
```d
import tanya.memory;
class A
{
this(int arg)
{
}
}
A a = defaultAllocator.make!A(5);
defaultAllocator.dispose(a);
```
As you can see, the user is responsible for deallocation, therefore `dispose`
is called at the end.
If you want to change the `defaultAllocator` to something different, you
probably want to do it at the program's beginning. Or you can invoke another
allocator directly. It is important to ensure that the object is destroyed
using the same allocator that was used to allocate it.
What if I get an allocated object from some function? The generic rule is: If
you haven't requested the memory yourself (with `make`), you don't need to free
it.
`tanya.memory.smartref` contains smart pointers, helpers that can take care of
a proper deallocation in some cases for you.
### Exceptions
Since exceptions are normal classes in D, they are allocated and dellocated the
same as described above, but:
1. The caller is **always** responsible for destroying a caught exception.
2. Exceptions are **always** allocated and should be always allocated with the
`defaultAllocator`.
```d
import tanya.memory;
void functionThatThrows()
{
throw defaultAlocator.make!Exception("An error occurred");
}
try
{
functionThatThrows()
}
catch (Exception e)
{
defaultAllocator.dispose(e);
}
```
### 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
so on. Containers can help here. The following example demonstrates how
`tanya.container.array.Array` can be used instead of `int[]`.
```d
import tanya.container.array;
Array!int arr;
// Reserve memory if I know that my container will contain approximately 15
// elements.
arr.reserve(15);
arr.insertBack(5); // Add one element.
arr.length = 10; // New elements are initialized to 0.
// Iterate over the first five elements.
foreach (el; arr[0 .. 5])
{
}
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 ### Supported compilers
* dmd 2.072.2 | DMD | GCC |
* dmd 2.071.2 |:-------:|:---------------:|
| 2.084.1 | gdc-8 (2.081.2) |
| 2.083.1 | gdc-7 (2.081.2) |
| 2.082.1 | |
### Current status ### Release management
The library is currently under development, but some parts of it can already be Tanya is still under active development and it isn't possible to provide great
used. backwards-compatibility at this stage. This won't change until 1.0.0. Almost
every release contains new features or API changes alongside bug fixes. Thus:
`network` and `async` exist for quite some time and could be better tested than - Patch releases add new functionality and bug fixes in a backwards-compatible
other components. manner
Containers were newly reworked and the API won't change significantly, but will - Minor releases contain API breakages
be only extended. The same is true for the `memory` package.
`math` package contains an arbitrary precision integer implementation that has - Major release number is always the same: `0.x.x`
a stable API (that mostly consists of operator overloads), but still needs
testing and work on its performance.
I'm currently mostly working on `crypto` that is not a complete cryptographic Deprecated functionality is where possible marked as such before getting
suite, but contains (will contain) algorithm implementations required by TLS. removed. It is left in the library for one release: If 0.8.1 deprecates some
feature, it is removed in the next release: 0.9.0.
### Further characteristics ## Further characteristics
* Tanya is a native D library. - Tanya is a native D library
* Documentation and usage examples can be found in the source code. - Tanya is cross-platform. The development happens on a 64-bit Linux, but it
Online documentation will be published soon. is being tested on Windows and FreeBSD as well
* Tanya is cross-platform. The development happens on a 64-bit Linux, but it - Tanya favours generic algorithms therefore there is no auto-decoding. Char
is being tested on Windows and FreeBSD as well. arrays are handled as any other array type
* The library isn't thread-safe. Thread-safity should be added later. - The library isn't thread-safe yet
## Contributing - Complex numbers (`cfloat`, `cdouble`, `creal`, `ifloat`, `idouble`, `ireal`)
aren't supported
Since I'm mostly busy writing new code and implementing new features I would
appreciate, if anyone uses the library. It would help me to improve the
codebase and fix issues.
Feel free to contact me if you have any questions. ## Feedback
Any feedback about your experience with tanya would be greatly appreciated. Feel free to
[contact me](mailto:info@caraus.de).

60
appveyor.yml Normal file
View File

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

14
arch/build.ninja Normal file
View File

@ -0,0 +1,14 @@
rule gas
command = gcc -c $in -o $out
rule archive
command = ar rcs $out $in
build abs.o: gas x64/linux/math/abs.S
build log.o: gas x64/linux/math/log.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 equal.o log.o abs.o

View File

@ -0,0 +1,8 @@
.text
.globl thrd_current
.type thrd_current, @function
thrd_current:
mov %fs:0, %rax
ret

35
arch/x64/linux/math/abs.S Normal file
View File

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

48
arch/x64/linux/math/log.S Normal file
View File

@ -0,0 +1,48 @@
.text
// logf.
.globl _D5tanya4math8nbtheory4logfFNaNbNiNffZf
.type _D5tanya4math8nbtheory4logfFNaNbNiNffZf, @function
_D5tanya4math8nbtheory4logfFNaNbNiNffZf:
movss %xmm0, -4(%rsp) // Put the argument onto the stack
fldln2 // Put lb(e) 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 _D5tanya4math8nbtheory3logFNaNbNiNfdZd
.type _D5tanya4math8nbtheory3logFNaNbNiNfdZd, @function
_D5tanya4math8nbtheory3logFNaNbNiNfdZd:
movsd %xmm0, -8(%rsp) // Put the argument onto the stack
fldln2 // Put lb(e) onto the FPU stack
fldl -8(%rsp) // Put a double onto the FPU stack
fyl2x // %st1 * lb(%st0)
// The result is on the FPU stack, but returned in %xmm0
fstpl -8(%rsp)
movsd -8(%rsp), %xmm0
ret
// logl.
.globl _D5tanya4math8nbtheory4loglFNaNbNiNfeZe
.type _D5tanya4math8nbtheory4loglFNaNbNiNfeZe, @function
_D5tanya4math8nbtheory4loglFNaNbNiNfeZe:
fldln2 // Put lb(e) onto the FPU stack
fldt 8(%rsp) // Put the argument onto the FPU stack
fyl2x // %st1 * lb(%st0)
ret

View File

@ -0,0 +1,67 @@
.text
/*
* copyMemory.
*
* rdi - source length
* rsi - source data.
* rdx - target length.
* rcx - target data.
*/
.globl _D5tanya6memory2op10copyMemoryFNaNbNixAvAvZv
.type _D5tanya6memory2op10copyMemoryFNaNbNixAvAvZv, @function
_D5tanya6memory2op10copyMemoryFNaNbNixAvAvZv:
mov %rdi, %rdx
mov %rcx, %rdi
cmp $0x08, %rdx
jc aligned_1
test $0x07, %edi
jz aligned_8
naligned:
movsb
dec %rdx
test $0x07, %edi
jnz naligned
aligned_8:
mov %rdx, %rcx
shr $0x03, %rcx
rep movsq
and $0x07, %edx
jz end
aligned_1:
// Write the remaining bytes
mov %rdx, %rcx
rep movsb
end:
ret
/*
* moveMemory.
*
* rdi - source length
* rsi - source data.
* rdx - target length.
* rcx - target data.
*/
.globl _D5tanya6memory2op10moveMemoryFNaNbNixAvAvZv
.type _D5tanya6memory2op10moveMemoryFNaNbNixAvAvZv, @function
_D5tanya6memory2op10moveMemoryFNaNbNixAvAvZv:
mov %rdi, %rdx
lea -1(%rdx, %rsi), %rsi
lea -1(%rdx, %rcx), %rdi
mov %rdx, %rcx
std // Set the direction flag
rep movsb
cld // Clear the direction flag
ret

View File

@ -0,0 +1,59 @@
.text
/*
* equalMemory.
*
* rdi - r1 length
* rsi - r1 data.
* rdx - r2 length.
* rcx - r2 data.
*/
.globl _D5tanya6memory2op11equalMemoryFNaNbNixAvxQdZb
.type _D5tanya6memory2op11equalMemoryFNaNbNixAvxQdZb, @function
_D5tanya6memory2op11equalMemoryFNaNbNixAvxQdZb:
// Compare the lengths
cmp %rdx, %rdi
jne not_equal
mov %rcx, %rdi
// Check if we're aligned
cmp $0x08, %rdx
jc aligned_1
test $0x07, %edi
jz aligned_8
naligned:
cmpsb
jne not_equal
dec %rdx
test $0x07, %edi
jnz naligned
aligned_8:
mov %rdx, %rcx
shr $0x03, %rcx
repe cmpsq
jne not_equal
and $0x07, %edx
jz equal
aligned_1: // Compare the remaining bytes
mov %rdx, %rcx
cmp $0x0, %rcx
repe cmpsb
jne not_equal
equal:
mov $0x01, %rax // Return 1
jmp end
not_equal:
xor %rax, %rax // Return 0
end:
ret

View File

@ -0,0 +1,160 @@
.text
/*
* fillMemory.
*
* rdi - length.
* rsi - pointer.
* rdx - value filled with a byte.
*/
.globl _D5tanya6memory2op10fillMemoryFNaNbNiAvmZv
.type _D5tanya6memory2op10fillMemoryFNaNbNiAvmZv, @function
_D5tanya6memory2op10fillMemoryFNaNbNiAvmZv:
// Check for zero length
test %rdi, %rdi
jz end
mov %rdi, %rax
mov %rsi, %r8
movq %rdx, %xmm0
movlhps %xmm0, %xmm0
// Check if the pointer is aligned to a 16-byte boundary
and $-0x10, %r8
// Compute the number of misaligned bytes
mov %rsi, %r9
sub %r8, %r9
test %r9, %r9
jz aligned
// Get the number of bytes to be written until we are aligned
mov $0x10, %rcx
sub %r9, %rcx
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
// Advance the pointer. Decrease the total number of bytes
// and the misaligned ones
inc %r8
dec %rcx
dec %rax
// Checks if we are aligned
test %rcx, %rcx
jnz naligned
aligned:
// Checks if we're done writing bytes
test %rax, %rax
jz end
// Write 1 byte at a time
cmp $8, %rax
jl aligned_1
// Write 8 bytes at a time
cmp $16, %rax
jl aligned_8
// Write 16 bytes at a time
cmp $32, %rax
jl aligned_16
// Write 32 bytes at a time
cmp $64, %rax
jl aligned_32
aligned_64:
movdqa %xmm0, (%r8)
movdqa %xmm0, 16(%r8)
movdqa %xmm0, 32(%r8)
movdqa %xmm0, 48(%r8)
add $64, %r8
sub $64, %rax
cmp $64, %rax
jge aligned_64
// Checks if we're done writing bytes
test %rax, %rax
jz end
// Write 1 byte at a time
cmp $8, %rax
jl aligned_1
// Write 8 bytes at a time
cmp $16, %rax
jl aligned_8
// Write 16 bytes at a time
cmp $32, %rax
jl aligned_16
aligned_32:
movdqa %xmm0, (%r8)
movdqa %xmm0, 16(%r8)
add $32, %r8
sub $32, %rax
// Checks if we're done writing bytes
test %rax, %rax
jz end
// Write 1 byte at a time
cmp $8, %rax
jl aligned_1
// Write 8 bytes at a time
cmp $16, %rax
jl aligned_8
aligned_16:
movdqa %xmm0, (%r8)
add $16, %r8
sub $16, %rax
// Checks if we're done writing bytes
test %rax, %rax
jz end
// Write 1 byte at a time
cmp $8, %rax
jl aligned_1
aligned_8:
mov %rdx, (%r8)
add $8, %r8
sub $8, %rax
// Checks if we're done writing bytes
test %rax, %rax
jz end
aligned_1:
mov %dl, (%r8)
inc %r8
dec %rax
test %rax, %rax
jnz aligned_1
end:
ret

65
arch/x64/linux/syscall.S Normal file
View File

@ -0,0 +1,65 @@
/*
The kernel uses the following registers:
%rdi, %rsi, %rdx, %r8, %r9, %r10
The number of the syscall is passed in %rax.
A syscall clobbers:
%rax, %rcx, %r11
The returned value is placed in %rax.
*/
.text
// 1 parameter.
.globl _D5tanya3sys5linux7syscallQiFNbNillZl
.type _D5tanya3sys5linux7syscallQiFNbNillZl, @function
_D5tanya3sys5linux7syscallQiFNbNillZl:
movq %rsi, %rax // Syscall number.
syscall
ret
// 2 parameters.
.globl _D5tanya3sys5linux7syscallQiFNbNilllZl
.type _D5tanya3sys5linux7syscallQiFNbNilllZl, @function
_D5tanya3sys5linux7syscallQiFNbNilllZl:
movq %rdx, %rax
syscall
ret
// 3 parameters.
.globl _D5tanya3sys5linux7syscallQiFNbNillllZl
.type _D5tanya3sys5linux7syscallQiFNbNillllZl, @function
_D5tanya3sys5linux7syscallQiFNbNillllZl:
movq %rcx, %rax
syscall
ret
// 6 parameters.
.globl _D5tanya3sys5linux7syscallQiFNbNilllllllZl
.type _D5tanya3sys5linux7syscallQiFNbNilllllllZl, @function
_D5tanya3sys5linux7syscallQiFNbNilllllllZl:
pushq %rbp
movq %rsp, %rbp
movq 16(%rbp), %rax
mov %rcx, %r10
syscall
leave
ret

3
codecov.yml Normal file
View File

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

81
dscanner.ini Normal file
View File

@ -0,0 +1,81 @@
; Configure which static analysis checks are skip-unittest
[analysis.config.StaticAnalysisConfig]
; Check variable, class, struct, interface, union, and function names against t
; he Phobos style guide
style_check="disabled"
; Check for array literals that cause unnecessary allocation
enum_array_literal_check="skip-unittest"
; Check for poor exception handling practices
exception_check="skip-unittest"
; Check for use of the deprecated 'delete' keyword
delete_check="skip-unittest"
; Check for use of the deprecated floating point operators
float_operator_check="skip-unittest"
; Check number literals for readability
number_style_check="disabled"
; Checks that opEquals, opCmp, toHash, and toString are either const, immutable
; , or inout.
object_const_check="disabled"
; Checks for .. expressions where the left side is larger than the right.
backwards_range_check="skip-unittest"
; Checks for if statements whose 'then' block is the same as the 'else' block
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="skip-unittest"
; Checks for unused labels
unused_label_check="skip-unittest"
; Checks for duplicate attributes
duplicate_attribute="skip-unittest"
; Checks that opEquals and toHash are both defined or neither are defined
opequals_tohash_check="disabled"
; Checks for subtraction from .length properties
length_subtraction_check="disabled"
; Checks for methods or properties whose names conflict with built-in propertie
; s
builtin_property_names_check="skip-unittest"
; Checks for confusing code in inline asm statements
asm_style_check="skip-unittest"
; Checks for confusing logical operator precedence
logical_precedence_check="skip-unittest"
; Checks for undocumented public declarations
undocumented_declaration_check="disabled"
; Checks for poor placement of function attributes
function_attribute_check="skip-unittest"
; Checks for use of the comma operator
comma_expression_check="skip-unittest"
; Checks for local imports that are too broad
local_import_check="disabled"
; Checks for variables that could be declared immutable
could_be_immutable_check="disabled"
; Checks for redundant expressions in if statements
redundant_if_check="skip-unittest"
; Checks for redundant parenthesis
redundant_parens_check="skip-unittest"
; Checks for mismatched argument and parameter names
mismatched_args_check="skip-unittest"
; Checks for labels with the same name as variables
label_var_same_name_check="disabled"
; Checks for lines longer than 120 characters
long_line_check="skip-unittest"
; Checks for assignment to auto-ref function parameters
auto_ref_assignment_check="disabled"
; Checks for incorrect infinite range definitions
incorrect_infinite_range_check="skip-unittest"
; Checks for asserts that are always true
useless_assert_check="skip-unittest"
; Check for uses of the old-style alias syntax
alias_syntax_check="disabled"
; Checks for else if that should be else static if
static_if_else_check="skip-unittest"
; Check for unclear lambda syntax
lambda_return_check="skip-unittest"
; Check for auto function without return statement
auto_function_check="skip-unittest"
; Check for sortedness of imports
imports_sortedness="skip-unittest"
; Check for explicitly annotated unittests
explicitly_annotated_unittests="disabled"
; Check for useless usage of the final attribute
final_attribute_check="skip-unittest"

View File

@ -1,17 +1,40 @@
{ {
"name": "tanya", "name": "tanya",
"description": "D library with event loop", "description": "@nogc library. Containers, networking, metaprogramming, memory management, utilities",
"license": "MPL-2.0", "license": "MPL-2.0",
"copyright": "(c) Eugene Wissner <info@caraus.de>", "copyright": "© Eugene Wissner <info@caraus.de>",
"authors": [ "authors": [
"Eugene Wissner" "Eugene Wissner"
], ],
"targetType": "library", "targetType": "library",
"dependencies-linux": {
"mir-linux-kernel": "~>1.0.0"
},
"configurations": [ "configurations": [
{ {
"name": "library" "name": "library",
"targetType": "staticLibrary",
"versions": ["TanyaPhobos"]
},
{
"name": "dynamic",
"targetType": "dynamicLibrary",
"versions": ["TanyaPhobos"]
},
{
"name": "native",
"targetType": "library",
"platforms": ["linux-x86_64-gdc"],
"preBuildCommands": ["ninja -C arch"],
"lflags": ["arch/tanya.a"],
"versions": ["TanyaNative"]
} }
] ],
"libs-windows": ["advapi32"],
"libs-windows-x86_mscoff": ["iphlpapi"],
"libs-windows-x86_64": ["iphlpapi"]
} }

View File

@ -0,0 +1,463 @@
/* 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.
* 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 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);
}
@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)));
}
/// 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);
}
@nogc nothrow pure @safe unittest
{
assert(min(cast(ubyte[]) []).empty);
}
/**
* 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);
}
@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)));
}
/// 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);
}
@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);
}
}
/**
* 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;
}
@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,855 @@
/* 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.
* 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.algorithm.mutation;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range;
import tanya.typecons;
version (unittest) import tanya.test.stub;
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;
}
}
@property auto ref front()
in
{
assert(!empty);
}
do
{
return this.source.front;
}
void popFront()
in
{
assert(!empty);
}
do
{
this.source.popFront();
--this.length_;
}
@property bool empty()
{
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()
{
return this.length_;
}
}
static if (hasAssignableElements!R)
{
@property void front(ref ElementType!R value)
in
{
assert(!empty);
}
do
{
this.source.front = value;
}
@property void front(ElementType!R value)
in
{
assert(!empty);
}
do
{
this.source.front = move(value);
}
}
static if (isForwardRange!R)
{
typeof(this) save()
{
return typeof(this)(this.source.save(), length);
}
}
static if (isRandomAccessRange!R)
{
@property auto ref back()
in
{
assert(!empty);
}
do
{
return this.source[this.length - 1];
}
void popBack()
in
{
assert(!empty);
}
do
{
--this.length_;
}
auto ref opIndex(size_t i)
in
{
assert(i < length);
}
do
{
return this.source[i];
}
static if (hasAssignableElements!R)
{
@property void back(ref ElementType!R value)
in
{
assert(!empty);
}
do
{
this.source[length - 1] = value;
}
@property void back(ElementType!R value)
in
{
assert(!empty);
}
do
{
this.source[length - 1] = move(value);
}
void opIndexAssign(ref ElementType!R value, size_t i)
in
{
assert(i < length);
}
do
{
this.source[i] = value;
}
void opIndexAssign(ElementType!R value, size_t i)
in
{
assert(i < length);
}
do
{
this.source[i] = move(value);
}
}
}
static if (!exactly && hasSlicing!R)
{
auto opSlice(size_t i, size_t j)
in
{
assert(i <= j);
assert(j <= length);
}
do
{
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);
}
// 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 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);
}
// 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);
}
}
// Reverse-access-order range returned by `retro`.
private struct Retro(Range)
{
Range source;
@disable this();
private this(Range source)
{
this.source = source;
}
Retro save()
{
return this;
}
@property auto ref front()
in (!empty)
{
return this.source.back;
}
void popFront()
in (!empty)
{
this.source.popBack();
}
@property auto ref back()
in (!empty)
{
return this.source.front;
}
void popBack()
in (!empty)
{
this.source.popFront();
}
@property bool empty()
{
return this.source.empty;
}
static if (hasLength!Range)
{
@property size_t length()
{
return this.source.length;
}
}
static if (isRandomAccessRange!Range && hasLength!Range)
{
auto ref opIndex(size_t i)
in (i < length)
{
return this.source[$ - ++i];
}
}
static if (hasLength!Range && hasSlicing!Range)
{
alias opDollar = length;
auto opSlice(size_t i, size_t j)
in
{
assert(i <= j);
assert(j <= length);
}
do
{
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)(return 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[]));
}
// 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);
}
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);
}
// 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,696 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Algorithms that modify its arguments.
*
* Copyright: Eugene Wissner 2017-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/mutation.d,
* tanya/algorithm/mutation.d)
*/
module tanya.algorithm.mutation;
import tanya.conv;
static import tanya.memory.op;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range;
version (unittest) import tanya.test.stub;
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)
{
tanya.memory.op.fill!0((cast(void*) &value)[0 .. size]);
}
else
{
tanya.memory.op.copy(typeid(T).initializer()[0 .. size],
(&value)[0 .. 1]);
}
}
}
/**
* Moves $(D_PARAM source) into $(D_PARAM target) assuming that
* $(D_PARAM target) isn't initialized.
*
* Moving the $(D_PARAM source) copies it into the $(D_PARAM target) and places
* the $(D_PARAM source) into a valid but unspecified state, which means that
* after moving $(D_PARAM source) can be destroyed or assigned a new value, but
* accessing it yields an unspecified value. No postblits or destructors are
* called. If the $(D_PARAM target) should be destroyed before, use
* $(D_PSYMBOL move).
*
* $(D_PARAM source) and $(D_PARAM target) must be different objects.
*
* Params:
* T = Object type.
* source = Source object.
* target = Target object.
*
* See_Also: $(D_PSYMBOL move),
* $(D_PSYMBOL hasElaborateCopyConstructor),
* $(D_PSYMBOL hasElaborateDestructor).
*
* Precondition: `&source !is &target`.
*/
void moveEmplace(T)(ref T source, ref T target) @system
in
{
assert(&source !is &target, "Source and target must be different");
}
do
{
static if (is(T == struct) || isStaticArray!T)
{
tanya.memory.op.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);
}
// Is pure.
@nogc nothrow pure @system unittest
{
struct S
{
this(this)
{
}
}
S source, target = void;
static assert(is(typeof({ moveEmplace(source, target); })));
}
// Moves nested.
@nogc nothrow pure @system unittest
{
struct Nested
{
void method() @nogc nothrow pure @safe
{
}
}
Nested source, target = void;
moveEmplace(source, target);
assert(source == target);
}
// Emplaces static arrays.
@nogc nothrow pure @system unittest
{
static struct S
{
size_t member;
this(size_t i) @nogc nothrow pure @safe
{
this.member = i;
}
~this() @nogc nothrow pure @safe
{
}
}
S[2] source = [ S(5), S(5) ], target = void;
moveEmplace(source, target);
assert(source[0].member == 0);
assert(target[0].member == 5);
assert(source[1].member == 0);
assert(target[1].member == 5);
}
/**
* Moves $(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);
}
// Moves if source is target.
@nogc nothrow pure @safe unittest
{
int x = 5;
move(x, x);
assert(x == 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);
}
/**
* Copies the $(D_PARAM source) range into the $(D_PARAM target) range.
*
* Params:
* Source = Input range type.
* Target = Output range type.
* source = Source input range.
* target = Target output range.
*
* Returns: $(D_PARAM target) range, whose front element is the one past the
* last element copied.
*
* Precondition: $(D_PARAM target) should be large enough to accept all
* $(D_PARAM source) elements.
*/
Target copy(Source, Target)(Source source, Target target)
if (isInputRange!Source && isOutputRange!(Target, ElementType!Source))
in
{
static if (hasLength!Source && hasLength!Target)
{
assert(target.length >= source.length);
}
}
do
{
alias E = ElementType!Source;
static if (isDynamicArray!Source
&& is(Unqual!E == ElementType!Target)
&& !hasElaborateCopyConstructor!E
&& !hasElaborateAssign!E
&& !hasElaborateDestructor!E)
{
if (source.ptr < target.ptr
&& (() @trusted => (target.ptr - source.ptr) < source.length)())
{
tanya.memory.op.copyBackward(source, target);
}
else if (source.ptr !is target.ptr)
{
tanya.memory.op.copy(source, target);
}
return target[source.length .. $];
}
else
{
for (; !source.empty; source.popFront())
{
put(target, source.front);
}
return target;
}
}
///
@nogc nothrow pure @safe unittest
{
import tanya.algorithm.comparison : equal;
const int[2] source = [1, 2];
int[2] target = [3, 4];
copy(source[], target[]);
assert(equal(source[], target[]));
}
// 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);
}
/**
* 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[]));
}
// [] 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);
}
/**
* 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;
emplace!(ElementType!Range)(cast(void[]) (p[0 .. 1]), value);
}
}
else
{
fill(range, value);
}
}
///
@nogc nothrow pure @safe unittest
{
import tanya.algorithm.comparison : equal;
int[6] actual = void;
const int[6] expected = [1, 1, 1, 1, 1, 1];
uninitializedFill(actual[], 1);
assert(equal(actual[], expected[]));
}
/**
* Initializes all elements of the $(D_PARAM range) assuming that they are
* uninitialized.
*
* Params:
* Range = Input range type
* range = Input range.
*/
void initializeAll(Range)(Range range) @trusted
if (isInputRange!Range && hasLvalueElements!Range)
{
import tanya.memory.op : copy, fill;
alias T = ElementType!Range;
static if (__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
{
import tanya.algorithm.comparison : equal;
int[2] actual = void;
const int[2] expected = [0, 0];
initializeAll(actual[]);
assert(equal(actual[], expected[]));
}
@nogc nothrow pure @safe unittest
{
NonCopyable[] nonCopyable;
initializeAll(nonCopyable);
}
/**
* Destroys all elements in the $(D_PARAM range).
*
* This function has effect only if the element type of $(D_PARAM Range) has
* an elaborate destructor, i.e. it is a $(D_PSYMBOL struct) with an explicit
* or generated by the compiler destructor.
*
* Params:
* Range = Input range type.
* range = Input range.
*/
void destroyAll(Range)(Range range)
if (isInputRange!Range && hasLvalueElements!Range)
{
static if (hasElaborateDestructor!(ElementType!Range))
{
foreach (ref e; range)
{
destroy(e);
}
}
}
///
@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))
{
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
{
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[]));
}
@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[]));
}

View File

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

View File

@ -0,0 +1,78 @@
/* 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.
* 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);
}
@nogc nothrow pure @safe unittest
{
static struct Range
{
private int counter = 3;
int front() const @nogc nothrow pure @safe
{
return this.counter;
}
void popFront() @nogc nothrow pure @safe
{
--this.counter;
}
bool empty() const @nogc nothrow pure @safe
{
return this.counter == 0;
}
}
Range range;
assert(count(range) == 3);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,10 +3,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Copyright: Eugene Wissner 2016. * Interface for the event loop implementations and the default event loop
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * chooser.
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
* *
* --- * ---
* import tanya.async; * import tanya.async;
@ -15,100 +13,132 @@
* *
* class EchoProtocol : TransmissionControlProtocol * class EchoProtocol : TransmissionControlProtocol
* { * {
* private DuplexTransport transport; * private DuplexTransport transport;
* *
* void received(ubyte[] data) @nogc * void received(in ubyte[] data) @nogc
* { * {
* transport.write(data); * ubyte[512] buffer;
* } * buffer[0 .. data.length] = data;
* transport.write(buffer[]);
* }
* *
* void connected(DuplexTransport transport) @nogc * void connected(DuplexTransport transport) @nogc
* { * {
* this.transport = transport; * this.transport = transport;
* } * }
* *
* void disconnected(SocketException e = null) @nogc * void disconnected(SocketException e) @nogc
* { * {
* } * }
* } * }
* *
* void main() * 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) * version (Windows)
* { * {
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.INET); * auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.inet);
* } * }
* else * else
* { * {
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.INET); * auto sock = defaultAllocator.make!StreamSocket(AddressFamily.inet);
* sock.blocking = false; * sock.blocking = false;
* } * }
* *
* sock.bind(address); * sock.bind(address);
* sock.listen(5); * sock.listen(5);
* *
* auto io = defaultAllocator.make!ConnectionWatcher(sock); * auto io = defaultAllocator.make!ConnectionWatcher(sock);
* io.setProtocol!EchoProtocol; * io.setProtocol!EchoProtocol;
* *
* defaultLoop.start(io); * defaultLoop.start(io);
* defaultLoop.run(); * defaultLoop.run();
* *
* sock.shutdown(); * sock.shutdown();
* defaultAllocator.dispose(io); * defaultAllocator.dispose(io);
* defaultAllocator.dispose(sock); * defaultAllocator.dispose(sock);
* defaultAllocator.dispose(address); * defaultAllocator.dispose(address);
* } * }
* --- * ---
*
* Copyright: Eugene Wissner 2016-2018.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/loop.d,
* tanya/async/loop.d)
*/ */
module tanya.async.loop; module tanya.async.loop;
import core.time; import core.time;
import std.algorithm.iteration;
import std.algorithm.mutation;
import std.typecons;
import tanya.async.protocol;
import tanya.async.transport; import tanya.async.transport;
import tanya.async.watcher; import tanya.async.watcher;
import tanya.bitmanip;
import tanya.container.buffer; import tanya.container.buffer;
import tanya.container.queue; import tanya.container.list;
import tanya.memory; import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket; import tanya.network.socket;
version (DisableBackends) version (DisableBackends)
{ {
} }
else version (D_Ddoc)
{
}
else version (linux) else version (linux)
{ {
import tanya.async.event.epoll; import tanya.async.event.epoll;
version = Epoll; version = Epoll;
} }
else version (Windows) else version (Windows)
{ {
import tanya.async.event.iocp; import tanya.async.event.iocp;
version = IOCP; version = IOCP;
} }
else version (OSX) else version (OSX)
{ {
version = Kqueue; version = Kqueue;
} }
else version (iOS) else version (iOS)
{ {
version = Kqueue; version = Kqueue;
} }
else version (FreeBSD) else version (FreeBSD)
{ {
version = Kqueue; version = Kqueue;
} }
else version (OpenBSD) else version (OpenBSD)
{ {
version = Kqueue; version = Kqueue;
} }
else version (DragonFlyBSD) else version (DragonFlyBSD)
{ {
version = Kqueue; 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;
}
}
} }
/** /**
@ -116,11 +146,11 @@ else version (DragonFlyBSD)
*/ */
enum Event : uint enum Event : uint
{ {
none = 0x00, /// No events. none = 0x00, /// No events.
read = 0x01, /// Non-blocking read call. read = 0x01, /// Non-blocking read call.
write = 0x02, /// Non-blocking write call. write = 0x02, /// Non-blocking write call.
accept = 0x04, /// Connection made. accept = 0x04, /// Connection made.
error = 0x80000000, /// Sent when an error occurs. error = 0x80000000, /// Sent when an error occurs.
} }
alias EventMask = BitFlags!Event; alias EventMask = BitFlags!Event;
@ -130,174 +160,193 @@ alias EventMask = BitFlags!Event;
*/ */
abstract class Loop abstract class Loop
{ {
/// Pending watchers. private bool done = true;
protected Queue!Watcher* pendings;
/// Ditto. /// Pending watchers.
protected Queue!Watcher* swapPendings; protected DList!Watcher pendings;
/** /**
* Returns: Maximal event count can be got at a time * Returns: Maximal event count can be got at a time
* (should be supported by the backend). * (should be supported by the backend).
*/ */
protected @property inout(uint) maxEvents() protected @property uint maxEvents()
inout const pure nothrow @safe @nogc const pure nothrow @safe @nogc
{ {
return 128U; return 128U;
} }
/** @nogc @system unittest
* Initializes the loop. {
*/ auto loop = defaultAllocator.make!TestLoop;
this() @nogc assert(loop.maxEvents == 64);
{
pendings = MmapPool.instance.make!(Queue!Watcher)(MmapPool.instance);
swapPendings = MmapPool.instance.make!(Queue!Watcher)(MmapPool.instance);
}
/** defaultAllocator.dispose(loop);
* Frees loop internals. }
*/
~this() @nogc
{
foreach (w; *pendings)
{
MmapPool.instance.dispose(w);
}
MmapPool.instance.dispose(pendings);
foreach (w; *swapPendings) /**
{ * Initializes the loop.
MmapPool.instance.dispose(w); */
} this() @nogc
MmapPool.instance.dispose(swapPendings); {
} }
/** /**
* Starts the loop. * Frees loop internals.
*/ */
void run() @nogc ~this() @nogc
{ {
done_ = false; for (; !this.pendings.empty; this.pendings.removeFront())
do {
{ defaultAllocator.dispose(this.pendings.front);
poll(); }
}
// Invoke pendings /**
foreach (ref w; *swapPendings) * Starts the loop.
{ */
w.invoke(); void run() @nogc
} {
swap(pendings, swapPendings); this.done = false;
} do
while (!done_); {
} poll();
/** // Invoke pendings
* Break out of the loop. for (; !this.pendings.empty; this.pendings.removeFront())
*/ {
void unloop() @safe pure nothrow @nogc this.pendings.front.invoke();
{ }
done_ = true; }
} while (!this.done);
}
/** /**
* Start watching. * Break out of the loop.
* */
* Params: void unloop() @safe pure nothrow @nogc
* watcher = Watcher. {
*/ this.done = true;
void start(ConnectionWatcher watcher) @nogc }
{
if (watcher.active)
{
return;
}
watcher.active = true;
reify(watcher, EventMask(Event.none), EventMask(Event.accept));
}
/** @nogc @system unittest
* Stop watching. {
* auto loop = defaultAllocator.make!TestLoop;
* Params: assert(loop.done);
* watcher = Watcher.
*/
void stop(ConnectionWatcher watcher) @nogc
{
if (!watcher.active)
{
return;
}
watcher.active = false;
reify(watcher, EventMask(Event.accept), EventMask(Event.none)); loop.run();
} assert(loop.done);
/** defaultAllocator.dispose(loop);
* Should be called if the backend configuration changes. }
*
* Params:
* watcher = Watcher.
* oldEvents = The events were already set.
* events = The events should be set.
*
* Returns: $(D_KEYWORD true) if the operation was successful.
*/
abstract protected bool reify(ConnectionWatcher watcher,
EventMask oldEvents,
EventMask events) @nogc;
/** @nogc @system unittest
* Returns: The blocking time. {
*/ auto loop = defaultAllocator.make!TestLoop;
protected @property inout(Duration) blockTime() auto watcher = defaultAllocator.make!DummyWatcher;
inout @safe pure nothrow @nogc loop.pendings.insertBack(watcher);
{
// Don't block if we have to do.
return swapPendings.empty ? blockTime_ : Duration.zero;
}
/** assert(!watcher.invoked);
* Sets the blocking time for IO watchers. loop.run();
* assert(watcher.invoked);
* Params:
* blockTime = The blocking time. Cannot be larger than
* $(D_PSYMBOL maxBlockTime).
*/
protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc
in
{
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
assert(!blockTime.isNegative);
}
body
{
blockTime_ = blockTime;
}
/** defaultAllocator.dispose(loop);
* Kills the watcher and closes the connection. defaultAllocator.dispose(watcher);
*/ }
protected void kill(IOWatcher watcher, SocketException exception) @nogc
{
watcher.socket.shutdown();
defaultAllocator.dispose(watcher.socket);
MmapPool.instance.dispose(watcher.transport);
watcher.exception = exception;
swapPendings.enqueue(watcher);
}
/** /**
* Does the actual polling. * Start watching.
*/ *
abstract protected void poll() @nogc; * Params:
* watcher = Watcher.
*/
void start(ConnectionWatcher watcher) @nogc
{
if (watcher.active)
{
return;
}
watcher.active = true;
/// Whether the event loop should be stopped. reify(watcher, EventMask(Event.none), EventMask(Event.accept));
private bool done_; }
/// Maximal block time. /**
protected Duration blockTime_ = 1.dur!"minutes"; * Stop watching.
*
* Params:
* watcher = Watcher.
*/
void stop(ConnectionWatcher watcher) @nogc
{
if (!watcher.active)
{
return;
}
watcher.active = false;
reify(watcher, EventMask(Event.accept), EventMask(Event.none));
}
/**
* Should be called if the backend configuration changes.
*
* Params:
* watcher = Watcher.
* oldEvents = The events were already set.
* events = The events should be set.
*
* Returns: $(D_KEYWORD true) if the operation was successful.
*/
abstract protected bool reify(SocketWatcher watcher,
EventMask oldEvents,
EventMask events) @nogc;
/**
* Returns: The blocking time.
*/
protected @property inout(Duration) blockTime()
inout @safe pure nothrow @nogc
{
// Don't block if we have to do.
return pendings.empty ? blockTime_ : Duration.zero;
}
/**
* Sets the blocking time for IO watchers.
*
* Params:
* blockTime = The blocking time. Cannot be larger than
* $(D_PSYMBOL maxBlockTime).
*/
protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc
in
{
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
assert(!blockTime.isNegative);
}
do
{
blockTime_ = blockTime;
}
@nogc @system unittest
{
auto loop = defaultAllocator.make!TestLoop;
assert(loop.blockTime == 1.dur!"minutes");
loop.blockTime = 2.dur!"minutes";
assert(loop.blockTime == 2.dur!"minutes");
defaultAllocator.dispose(loop);
}
/**
* Does the actual polling.
*/
abstract protected void poll() @nogc;
/// Maximal block time.
protected Duration blockTime_ = 1.dur!"minutes";
} }
/** /**
@ -305,17 +354,17 @@ abstract class Loop
*/ */
class BadLoopException : Exception class BadLoopException : Exception
{ {
/** /**
* Params: * Params:
* file = The file where the exception occurred. * file = The file where the exception occurred.
* line = The line number where the exception occurred. * line = The line number where the exception occurred.
* next = The previous exception in the chain of exceptions, if any. * next = The previous exception in the chain of exceptions, if any.
*/ */
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
pure nothrow const @safe @nogc pure nothrow const @safe @nogc
{ {
super("Event loop cannot be initialized.", file, line, next); super("Event loop cannot be initialized.", file, line, next);
} }
} }
/** /**
@ -327,24 +376,24 @@ class BadLoopException : Exception
*/ */
@property Loop defaultLoop() @nogc @property Loop defaultLoop() @nogc
{ {
if (defaultLoop_ !is null) if (defaultLoop_ !is null)
{ {
return defaultLoop_; return defaultLoop_;
} }
version (Epoll) version (Epoll)
{ {
defaultLoop_ = MmapPool.instance.make!EpollLoop; defaultLoop_ = defaultAllocator.make!EpollLoop;
} }
else version (IOCP) else version (IOCP)
{ {
defaultLoop_ = MmapPool.instance.make!IOCPLoop; defaultLoop_ = defaultAllocator.make!IOCPLoop;
} }
else version (Kqueue) else version (Kqueue)
{ {
import tanya.async.event.kqueue; import tanya.async.event.kqueue;
defaultLoop_ = MmapPool.instance.make!KqueueLoop; defaultLoop_ = defaultAllocator.make!KqueueLoop;
} }
return defaultLoop_; return defaultLoop_;
} }
/** /**
@ -356,16 +405,29 @@ class BadLoopException : Exception
* your implementation to this property. * your implementation to this property.
* *
* Params: * Params:
* loop = The event loop. * loop = The event loop.
*/ */
@property void defaultLoop(Loop loop) @nogc @property void defaultLoop(Loop loop) @nogc
in in
{ {
assert(loop !is null); assert(loop !is null);
} }
body do
{ {
defaultLoop_ = loop; defaultLoop_ = loop;
} }
private Loop defaultLoop_; private Loop defaultLoop_;
@nogc @system 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

@ -3,10 +3,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Copyright: Eugene Wissner 2016. * This package provides asynchronous capabilities.
*
* Copyright: Eugene Wissner 2016-2018.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/package.d,
* tanya/async/package.d)
*/ */
module tanya.async; module tanya.async;

View File

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

View File

@ -3,13 +3,19 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Copyright: Eugene Wissner 2016. * This module contains transports which are responsible for data dilvery
* between two parties of an asynchronous communication.
*
* Copyright: Eugene Wissner 2016-2018.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/transport.d,
* tanya/async/transport.d)
*/ */
module tanya.async.transport; module tanya.async.transport;
import tanya.async.protocol;
import tanya.network.socket; import tanya.network.socket;
/** /**
@ -31,13 +37,13 @@ interface ReadTransport : Transport
*/ */
interface WriteTransport : Transport interface WriteTransport : Transport
{ {
/** /**
* Write some data to the transport. * Write some data to the transport.
* *
* Params: * Params:
* data = Data to send. * data = Data to send.
*/ */
void write(ubyte[] data) @nogc; void write(ubyte[] data) @nogc;
} }
/** /**
@ -45,6 +51,45 @@ interface WriteTransport : Transport
*/ */
interface DuplexTransport : ReadTransport, WriteTransport interface DuplexTransport : ReadTransport, WriteTransport
{ {
/**
* Returns: Application protocol.
*
* Postcondition: $(D_INLINECODE protocol !is null)
*/
@property Protocol protocol() pure nothrow @safe @nogc
out (protocol)
{
assert(protocol !is null);
}
/**
* Switches the protocol.
*
* The protocol is deallocated by the event loop.
*
* Params:
* protocol = Application protocol.
*
* Precondition: $(D_INLINECODE protocol !is null)
*/
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
in
{
assert(protocol !is null);
}
/**
* Returns $(D_PARAM true) if the transport is closing or closed.
*/
bool isClosing() const pure nothrow @safe @nogc;
/**
* Close the transport.
*
* Buffered data will be flushed. No more data will be received.
*/
void close() @nogc;
} }
/** /**
@ -52,12 +97,8 @@ interface DuplexTransport : ReadTransport, WriteTransport
*/ */
interface SocketTransport : Transport interface SocketTransport : Transport
{ {
@property inout(Socket) socket() inout pure nothrow @safe @nogc; /**
} * Returns: Socket.
*/
/** @property Socket socket() pure nothrow @safe @nogc;
* Represents a connection-oriented socket transport.
*/
package interface StreamTransport : DuplexTransport, SocketTransport
{
} }

View File

@ -3,243 +3,128 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Copyright: Eugene Wissner 2016. * Watchers register user's interest in some event.
*
* Copyright: Eugene Wissner 2016-2018.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/watcher.d,
* tanya/async/watcher.d)
*/ */
module tanya.async.watcher; module tanya.async.watcher;
import std.functional;
import std.exception;
import tanya.async.loop; import tanya.async.loop;
import tanya.async.protocol; import tanya.async.protocol;
import tanya.async.transport; import tanya.async.transport;
import tanya.container.buffer; import tanya.container.buffer;
import tanya.container.queue; import tanya.container.list;
import tanya.memory; import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket; import tanya.network.socket;
version (Windows)
{
import core.sys.windows.basetyps;
import core.sys.windows.mswsock;
import core.sys.windows.winbase;
import core.sys.windows.windef;
import core.sys.windows.winsock2;
}
/** /**
* A watcher is an opaque structure that you allocate and register to record * A watcher is an opaque structure that you allocate and register to record
* your interest in some event. * your interest in some event.
*/ */
abstract class Watcher abstract class Watcher
{ {
/// Whether the watcher is active. /// Whether the watcher is active.
bool active; bool active;
/** /**
* Invoke some action on event. * Invoke some action on event.
*/ */
void invoke() @nogc; void invoke() @nogc;
} }
class ConnectionWatcher : Watcher version (unittest)
{ {
/// Watched socket. final class DummyWatcher : Watcher
private Socket socket_; {
bool invoked;
/// Protocol factory. override void invoke() @nogc
protected Protocol delegate() @nogc protocolFactory; {
this.invoked = true;
package Queue!IOWatcher incoming; }
}
/**
* Params:
* socket = Socket.
*/
this(Socket socket) @nogc
{
socket_ = socket;
}
/// Ditto.
protected this() pure nothrow @safe @nogc
{
}
~this() @nogc
{
foreach (w; incoming)
{
MmapPool.instance.dispose(w);
}
}
/**
* Params:
* P = Protocol should be used.
*/
void setProtocol(P : Protocol)() @nogc
{
this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P;
}
/**
* Returns: Socket.
*/
@property inout(Socket) socket() inout pure nothrow @nogc
{
return socket_;
}
/**
* Returns: New protocol instance.
*/
@property Protocol protocol() @nogc
in
{
assert(protocolFactory !is null, "Protocol isn't set.");
}
body
{
return protocolFactory();
}
/**
* Invokes new connection callback.
*/
override void invoke() @nogc
{
foreach (io; incoming)
{
io.protocol.connected(cast(DuplexTransport) io.transport);
}
}
} }
/** /**
* Contains a pending watcher with the invoked events or a transport can be * Socket watcher.
* read from.
*/ */
class IOWatcher : ConnectionWatcher abstract class SocketWatcher : Watcher
{ {
/// If an exception was thrown the transport should be already invalid. /// Watched socket.
private union protected Socket socket_;
{
StreamTransport transport_;
SocketException exception_;
}
private Protocol protocol_; /**
* Params:
* socket = Socket.
*
* Precondition: $(D_INLINECODE socket !is null)
*/
this(Socket socket) pure nothrow @safe @nogc
in
{
assert(socket !is null);
}
do
{
socket_ = socket;
}
/** /**
* Returns: Underlying output buffer. * Returns: Socket.
*/ */
package ReadBuffer!ubyte output; @property Socket socket() pure nothrow @safe @nogc
{
/** return socket_;
* Params: }
* transport = Transport. }
* protocol = New instance of the application protocol.
*/ /**
this(StreamTransport transport, Protocol protocol) @nogc * Connection watcher.
in */
{ class ConnectionWatcher : SocketWatcher
assert(transport !is null); {
assert(protocol !is null); /// Incoming connection queue.
} DList!DuplexTransport incoming;
body
{ private Protocol delegate() @nogc protocolFactory;
super();
transport_ = transport; /**
protocol_ = protocol; * Params:
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance); * socket = Socket.
active = true; */
} this(Socket socket) @nogc
{
/** super(socket);
* Destroys the watcher. }
*/
~this() @nogc /**
{ * Params:
MmapPool.instance.dispose(protocol_); * P = Protocol should be used.
} */
void setProtocol(P : Protocol)() @nogc
/** {
* Assigns a transport. this.protocolFactory = () @nogc => cast(Protocol) defaultAllocator.make!P;
* }
* Params:
* transport = Transport. /**
* protocol = Application protocol. * Invokes new connection callback.
* */
* Returns: $(D_KEYWORD this). override void invoke() @nogc
*/ in
IOWatcher opCall(StreamTransport transport, Protocol protocol) {
pure nothrow @nogc assert(protocolFactory !is null, "Protocol isn't set.");
in }
{ do
assert(transport !is null); {
assert(protocol !is null); for (; !this.incoming.empty; this.incoming.removeFront())
} {
body this.incoming.front.protocol = protocolFactory();
{ this.incoming.front.protocol.connected(this.incoming.front);
transport_ = transport; }
protocol_ = protocol; }
active = true;
return this;
}
/**
* Returns: Transport used by this watcher.
*/
@property inout(StreamTransport) transport() inout pure nothrow @nogc
{
return transport_;
}
/**
* Sets an exception occurred during a read/write operation.
*
* Params:
* exception = Thrown exception.
*/
@property void exception(SocketException exception) pure nothrow @nogc
{
exception_ = exception;
}
/**
* Returns: Application protocol.
*/
override @property Protocol protocol() pure nothrow @safe @nogc
{
return protocol_;
}
/**
* Returns: Socket.
*/
override @property inout(Socket) socket() inout pure nothrow @nogc
{
return transport.socket;
}
/**
* Invokes the watcher callback.
*/
override void invoke() @nogc
{
if (output.length)
{
protocol.received(output[0..$]);
output.clear();
}
else
{
protocol.disconnected(exception_);
active = false;
}
}
} }

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

@ -0,0 +1,359 @@
/* 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.
* 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;
}
}
@nogc nothrow pure @safe unittest
{
enum E : int
{
one = 1,
}
// Casts to a boolean
assert(BitFlags!E(E.one));
assert(!BitFlags!E());
// Assigns to and compares with a single value
{
BitFlags!E bitFlags;
bitFlags = E.one;
assert(bitFlags == E.one);
}
// Assigns to and compares with the same type
{
auto bitFlags1 = BitFlags!E(E.one);
BitFlags!E bitFlags2;
bitFlags2 = bitFlags1;
assert(bitFlags1 == bitFlags2);
}
assert((BitFlags!E() | E.one) == BitFlags!E(E.one));
assert((BitFlags!E() | BitFlags!E(E.one)) == BitFlags!E(E.one));
assert(!(BitFlags!E() & BitFlags!E(E.one)));
assert(!(BitFlags!E(E.one) ^ E.one));
assert(BitFlags!E() ^ BitFlags!E(E.one));
assert(~BitFlags!E());
assert(BitFlags!E().toHash() == 0);
assert(BitFlags!E(E.one).toHash() != 0);
// opBinaryRight is allowed
static assert(is(typeof({ E.one | BitFlags!E(); })));
}
/**
* 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

File diff suppressed because it is too large Load Diff

View File

@ -5,41 +5,335 @@
/* /*
* Internal package used by containers that rely on entries/nodes. * Internal package used by containers that rely on entries/nodes.
* *
* Copyright: Eugene Wissner 2016. * Copyright: Eugene Wissner 2016-2018.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/container/entry.d,
* tanya/container/entry.d)
*/ */
module tanya.container.entry; module tanya.container.entry;
version (unittest) import tanya.algorithm.mutation;
import tanya.container.array;
import tanya.memory.allocator;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.typecons;
version (unittest) import tanya.test.stub;
package struct SEntry(T)
{ {
package struct ConstEqualsStruct // Item content.
{ T content;
int opEquals(typeof(this) that) const @nogc
{
return true;
}
}
package struct MutableEqualsStruct // Next item.
{ SEntry* next;
int opEquals(typeof(this) that) @nogc
{
return true;
}
}
package struct NoEqualsStruct
{
}
} }
package struct Entry(T) package struct DEntry(T)
{ {
/// Item content. // Item content.
T content; T content;
/// Next item. // Previous and next item.
Entry* next; DEntry* next, prev;
}
package enum BucketStatus : byte
{
deleted = -1,
empty = 0,
used = 1,
}
package struct Bucket(K, V = void)
{
static if (is(V == void))
{
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(K) key() inout
{
static if (is(V == void))
{
return this.key_;
}
else
{
return this.kv.key;
}
}
void moveKey(ref K key)
{
move(key, this.key());
this.status = BucketStatus.used;
}
bool opEquals(T)(ref const T key) const
{
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!K)
{
destroy(key);
}
this.status = BucketStatus.deleted;
}
}
// 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 struct HashArray(alias hasher, K, V = void)
{
alias Key = K;
alias Value = V;
alias Bucket = .Bucket!(Key, Value);
alias Buckets = Array!Bucket;
Buckets array;
size_t lengthIndex;
size_t length;
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
do
{
this.array = Buckets(allocator);
}
this(T)(ref T data, shared Allocator allocator)
if (is(Unqual!T == HashArray))
in
{
assert(allocator !is null);
}
do
{
this.array = Buckets(data.array, allocator);
this.lengthIndex = data.lengthIndex;
this.length = data.length;
}
// Move constructor
void move(ref HashArray data, shared Allocator allocator)
in
{
assert(allocator !is null);
}
do
{
this.array = Buckets(.move(data.array), allocator);
this.lengthIndex = data.lengthIndex;
this.length = data.length;
}
void swap(ref HashArray data)
{
.swap(this.array, data.array);
.swap(this.lengthIndex, data.lengthIndex);
.swap(this.length, data.length);
}
void opAssign()(ref typeof(this) that)
{
this.array = that.array;
this.lengthIndex = that.lengthIndex;
this.length = that.length;
}
@property size_t bucketCount() const
{
return primes[this.lengthIndex];
}
/*
* Returns bucket position for `hash`. `0` may mean the 0th position or an
* empty `buckets` array.
*/
size_t locateBucket(T)(ref const T key) const
{
return this.array.length == 0 ? 0 : hasher(key) % bucketCount;
}
/*
* If the key doesn't already exists, returns an empty bucket the key can
* be inserted in and adjusts the element count. Otherwise returns the
* bucket containing the key.
*/
ref Bucket insert(ref Key key)
{
const newLengthIndex = this.lengthIndex + 1;
if (newLengthIndex != primes.length)
{
foreach (ref e; this.array[locateBucket(key) .. $])
{
if (e == key)
{
return e;
}
else if (e.status != BucketStatus.used)
{
++this.length;
return e;
}
}
this.rehashToSize(newLengthIndex);
}
foreach (ref e; this.array[locateBucket(key) .. $])
{
if (e == key)
{
return e;
}
else if (e.status != BucketStatus.used)
{
++this.length;
return e;
}
}
this.array.length = this.array.length + 1;
++this.length;
return this.array[$ - 1];
}
// Takes an index in the primes array.
void rehashToSize(const size_t n)
in
{
assert(n < primes.length);
}
do
{
auto storage = typeof(this.array)(primes[n], this.array.allocator);
DataLoop: foreach (ref e1; this.array[])
{
if (e1.status == BucketStatus.used)
{
auto bucketPosition = hasher(e1.key) % primes[n];
foreach (ref e2; storage[bucketPosition .. $])
{
if (e2.status != BucketStatus.used) // Insert the key
{
.move(e1, e2);
continue DataLoop;
}
}
storage.insertBack(.move(e1));
}
}
.move(storage, this.array);
this.lengthIndex = n;
}
void rehash(const size_t n)
{
size_t lengthIndex;
for (; lengthIndex < primes.length; ++lengthIndex)
{
if (primes[lengthIndex] >= n)
{
break;
}
}
if (lengthIndex > this.lengthIndex)
{
this.rehashToSize(lengthIndex);
}
}
@property size_t capacity() const
{
return this.array.length;
}
void clear()
{
this.array.clear();
this.length = 0;
}
size_t remove(ref Key key)
{
foreach (ref e; this.array[locateBucket(key) .. $])
{
if (e == key) // Found.
{
e.remove();
--this.length;
return 1;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return 0;
}
bool opBinaryRight(string op : "in", T)(ref const T key) const
{
foreach (ref e; this.array[locateBucket(key) .. $])
{
if (e == key) // Found.
{
return true;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return false;
}
}
// Can be constructed with non-copyable key/values
@nogc nothrow pure @safe unittest
{
static assert(is(Bucket!NonCopyable));
static assert(is(Bucket!(NonCopyable, NonCopyable)));
static assert(is(HashArray!((ref NonCopyable) => 0U, NonCopyable)));
static assert(is(HashArray!((ref NonCopyable) => 0U, NonCopyable, NonCopyable)));
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,14 +3,20 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Copyright: Eugene Wissner 2016. * Abstract data types whose instances are collections of other objects.
*
* Copyright: Eugene Wissner 2016-2018.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/container/package.d,
* tanya/container/package.d)
*/ */
module tanya.container; module tanya.container;
public import tanya.container.array;
public import tanya.container.buffer; public import tanya.container.buffer;
public import tanya.container.hashtable;
public import tanya.container.list; public import tanya.container.list;
public import tanya.container.vector; public import tanya.container.set;
public import tanya.container.queue; public import tanya.container.string;

View File

@ -1,360 +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 2016.
* 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.container.queue;
import std.traits;
import std.algorithm.mutation;
import tanya.container.entry;
import tanya.memory;
/**
* FIFO queue.
*
* Params:
* T = Content type.
*/
struct Queue(T)
{
/**
* Removes all elements from the queue.
*/
~this()
{
while (!empty)
{
dequeue();
}
}
/**
* Removes all elements from the queue.
*/
deprecated
void clear()
{
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(Entry!T)* i = first.next; 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);
}
version (D_Ddoc)
{
/**
* Compares two queues. Checks if all elements of the both queues are equal.
*
* Returns: Whether $(D_KEYWORD this) and $(D_PARAM that) are equal.
*/
deprecated
int opEquals(ref typeof(this) that);
/// Ditto.
deprecated
int opEquals(typeof(this) that);
}
else static if (!hasMember!(T, "opEquals")
|| (functionAttributes!(T.opEquals) & FunctionAttribute.const_))
{
deprecated
bool opEquals(in ref typeof(this) that) const
{
const(Entry!T)* i = first.next;
const(Entry!T)* j = that.first.next;
while (i !is null && j !is null)
{
if (i.content != j.content)
{
return false;
}
i = i.next;
j = j.next;
}
return i is null && j is null;
}
deprecated
bool opEquals(in typeof(this) that) const
{
return opEquals(that);
}
}
else
{
deprecated
bool opEquals(ref typeof(this) that)
{
Entry!T* i = first.next;
Entry!T* j = that.first.next;
while (i !is null && j !is null)
{
if (i.content != j.content)
{
return false;
}
i = i.next;
j = j.next;
}
return i is null && j is null;
}
deprecated
bool opEquals(typeof(this) that)
{
return opEquals(that);
}
}
/**
* Returns: First element.
*/
deprecated("Use dequeue instead.")
@property ref inout(T) front() inout
in
{
assert(!empty);
}
body
{
return first.next.content;
}
/**
* Inserts a new element.
*
* Params:
* x = New element.
*
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) enqueue(ref T x)
{
auto temp = allocator.make!(Entry!T)(x);
if (empty)
{
first.next = rear = temp;
}
else
{
rear.next = temp;
rear = rear.next;
}
return this;
}
/// Ditto.
ref typeof(this) enqueue(T x)
{
return enqueue(x);
}
deprecated("Use enqueue instead.")
alias insert = enqueue;
deprecated("Use enqueue instead.")
alias insertBack = enqueue;
///
unittest
{
Queue!int q;
assert(q.empty);
q.enqueue(8).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.next 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);
assert(allocator !is null);
}
body
{
auto n = first.next.next;
T ret = move(first.next.content);
dispose(allocator, first.next);
first.next = n;
return ret;
}
deprecated("Use dequeue instead.")
alias popFront = dequeue;
///
unittest
{
Queue!int q;
q.enqueue(8).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.
*/
int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
{
int result;
for (size_t i = 0; !empty; ++i)
{
auto e = dequeue();
if ((result = dg(i, e)) != 0)
{
return result;
}
}
return result;
}
/// Ditto.
int opApply(scope int delegate(ref T) @nogc dg)
{
int result;
while (!empty)
{
auto e = dequeue();
if ((result = dg(e)) != 0)
{
return result;
}
}
return result;
}
///
unittest
{
Queue!int q;
size_t j;
q.enqueue(5).enqueue(4).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).enqueue(4).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);
}
/// The first element of the list.
private Entry!T first;
/// The last element of the list.
private Entry!T* rear;
mixin DefaultAllocator;
}
///
unittest
{
Queue!int q;
q.enqueue(5);
assert(!q.empty);
q.enqueue(4).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

@ -0,0 +1,787 @@
/* 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 implements a $(D_PSYMBOL Set) container that stores unique
* values without any particular order.
*
* Copyright: Eugene Wissner 2017-2018.
* 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/set.d,
* tanya/container/set.d)
*/
module tanya.container.set;
import tanya.container.array;
import tanya.container.entry;
import tanya.hash.lookup;
import tanya.memory;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range.primitive;
version (unittest) import tanya.test.stub;
/**
* Bidirectional range that iterates over the $(D_PSYMBOL Set)'s values.
*
* Params:
* T = Type of the internal hash storage.
*/
struct Range(T)
{
private alias E = 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 Range save()
{
return this;
}
@property bool empty() const
{
return this.dataRange.empty();
}
void popFront()
in
{
assert(!empty);
assert(this.dataRange.front.status == BucketStatus.used);
}
out
{
assert(empty || this.dataRange.back.status == BucketStatus.used);
}
do
{
do
{
this.dataRange.popFront();
}
while (!empty && dataRange.front.status != BucketStatus.used);
}
void popBack()
in
{
assert(!empty);
assert(this.dataRange.back.status == BucketStatus.used);
}
out
{
assert(empty || this.dataRange.back.status == BucketStatus.used);
}
do
{
do
{
this.dataRange.popBack();
}
while (!empty && dataRange.back.status != BucketStatus.used);
}
@property ref inout(E) front() inout
in
{
assert(!empty);
assert(this.dataRange.front.status == BucketStatus.used);
}
do
{
return this.dataRange.front.key;
}
@property ref inout(E) back() inout
in
{
assert(!empty);
assert(this.dataRange.back.status == BucketStatus.used);
}
do
{
return this.dataRange.back.key;
}
Range opIndex()
{
return typeof(return)(this.dataRange[]);
}
Range!(const T) opIndex() const
{
return typeof(return)(this.dataRange[]);
}
}
/**
* Set is a data structure that stores unique values without any particular
* order.
*
* This $(D_PSYMBOL Set) is implemented using closed hashing. Hash collisions
* are resolved with linear probing.
*
* $(D_PARAM T) should be hashable with $(D_PARAM hasher). $(D_PARAM hasher) is
* a callable that accepts an argument of type $(D_PARAM T) and returns a hash
* value for it ($(D_KEYWORD size_t)).
*
* Params:
* T = Element type.
* hasher = Hash function for $(D_PARAM T).
*/
struct Set(T, 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!HashArray;
/// ditto
alias ConstRange = .Range!(const HashArray);
invariant
{
assert(this.data.lengthIndex < primes.length);
assert(this.data.array.length == 0
|| this.data.array.length == primes[this.data.lengthIndex]);
}
/**
* Constructor.
*
* Params:
* n = Minimum number of buckets.
* allocator = Allocator.
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
this(size_t n, shared Allocator allocator = defaultAllocator)
in
{
assert(allocator !is null);
}
do
{
this(allocator);
this.data.rehash(n);
}
///
@nogc nothrow pure @safe unittest
{
auto set = Set!int(5);
assert(set.capacity == 7);
}
/// ditto
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
do
{
this.data = HashArray(allocator);
}
/**
* Initializes this $(D_PARAM Set) 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 == Set))
in
{
assert(allocator !is null);
}
do
{
this.data = HashArray(init.data, allocator);
}
/// ditto
this(S)(S init, shared Allocator allocator = defaultAllocator)
if (is(S == Set))
in
{
assert(allocator !is null);
}
do
{
this.data.move(init.data, allocator);
}
/**
* Initializes the set from a forward range.
*
* Params:
* R = Range type.
* range = Forward range.
* allocator = Allocator.
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
this(R)(R range, shared Allocator allocator = defaultAllocator)
if (isForwardRange!R && isImplicitlyConvertible!(ElementType!R, T))
in
{
assert(allocator !is null);
}
do
{
insert(range);
}
///
@nogc nothrow pure @safe unittest
{
int[2] range = [1, 2];
Set!int set = Set!int(range[]);
assert(1 in set);
assert(2 in set);
}
/**
* Initializes the set from a static array.
*
* Params:
* n = Array size.
* array = Static array.
* allocator = Allocator.
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
this(size_t n)(T[n] array, shared Allocator allocator = defaultAllocator)
in
{
assert(allocator !is null);
}
do
{
insert(array[]);
}
///
@nogc nothrow pure @safe unittest
{
Set!int set = Set!int([1, 2]);
assert(1 in set);
assert(2 in set);
}
/**
* Assigns another set.
*
* 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 == Set))
{
this.data = that.data;
return this;
}
/// ditto
ref typeof(this) opAssign(S)(S that) @trusted
if (is(S == Set))
{
this.data.swap(that.data);
return this;
}
/**
* Returns: Used allocator.
*
* Postcondition: $(D_INLINECODE allocator !is null)
*/
@property shared(Allocator) allocator() const
out (allocator)
{
assert(allocator !is null);
}
do
{
return this.data.array.allocator;
}
/**
* Maximum amount of elements this $(D_PSYMBOL Set) 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 Set) capacity.
*/
@property size_t capacity() const
{
return this.data.capacity;
}
///
@nogc nothrow pure @safe unittest
{
Set!int set;
assert(set.capacity == 0);
set.insert(8);
assert(set.capacity == 3);
}
/**
* Iterates over the $(D_PSYMBOL Set) and counts the elements.
*
* Returns: Count of elements within the $(D_PSYMBOL Set).
*/
@property size_t length() const
{
return this.data.length;
}
///
@nogc nothrow pure @safe unittest
{
Set!int set;
assert(set.length == 0);
set.insert(8);
assert(set.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
{
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];
/**
* Inserts a new element.
*
* Params:
* value = Element value.
*
* Returns: Amount of new elements inserted.
*/
size_t insert()(ref T value)
{
auto e = ((ref v) @trusted => &this.data.insert(v))(value);
if (e.status != BucketStatus.used)
{
e.moveKey(value);
return 1;
}
return 0;
}
size_t insert()(T value)
{
auto e = ((ref v) @trusted => &this.data.insert(v))(value);
if (e.status != BucketStatus.used)
{
e.key = value;
return 1;
}
return 0;
}
///
@nogc nothrow pure @safe unittest
{
Set!int set;
assert(8 !in set);
assert(set.insert(8) == 1);
assert(set.length == 1);
assert(8 in set);
assert(set.insert(8) == 0);
assert(set.length == 1);
assert(8 in set);
assert(set.remove(8));
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)(R range)
if (isForwardRange!R && isImplicitlyConvertible!(ElementType!R, T))
{
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.
*
* Params:
* value = Element value.
*
* Returns: Number of elements removed, which is in the container with
* unique values `1` if an element existed, and `0` otherwise.
*/
size_t remove(T value)
{
return this.data.remove(value);
}
///
@nogc nothrow pure @safe unittest
{
Set!int set;
set.insert(8);
assert(8 in set);
assert(set.remove(8) == 1);
assert(set.remove(8) == 0);
assert(8 !in set);
}
/**
* $(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", U)(auto ref const U value) const
if (ifTestable!(U, a => T.init == a))
{
return value in this.data;
}
///
@nogc nothrow pure @safe unittest
{
Set!int set;
assert(5 !in set);
set.insert(5);
assert(5 in set);
assert(8 !in set);
}
/**
* 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 over the container.
*
* 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
{
Set!int set;
assert(set[].empty);
set.insert(8);
assert(!set[].empty);
assert(set[].front == 8);
assert(set[].back == 8);
}
}
// Basic insertion logic.
@nogc nothrow pure @safe unittest
{
Set!int set;
assert(set.insert(5) == 1);
assert(5 in set);
assert(set.data.array.length == 3);
assert(set.insert(5) == 0);
assert(5 in set);
assert(set.data.array.length == 3);
assert(set.insert(9) == 1);
assert(9 in set);
assert(5 in set);
assert(set.data.array.length == 3);
assert(set.insert(7) == 1);
assert(set.insert(8) == 1);
assert(8 in set);
assert(5 in set);
assert(9 in set);
assert(7 in set);
assert(set.data.array.length == 7);
assert(set.insert(16) == 1);
assert(16 in set);
assert(set.data.array.length == 7);
}
// Static checks.
@nogc nothrow pure @safe unittest
{
import tanya.range.primitive;
static assert(isBidirectionalRange!(Set!int.ConstRange));
static assert(isBidirectionalRange!(Set!int.Range));
static assert(!isInfinite!(Set!int.Range));
static assert(!hasLength!(Set!int.Range));
static assert(is(Set!uint));
static assert(is(Set!long));
static assert(is(Set!ulong));
static assert(is(Set!short));
static assert(is(Set!ushort));
static assert(is(Set!bool));
}
@nogc nothrow pure @safe unittest
{
const Set!int set;
assert(set[].empty);
}
@nogc nothrow pure @safe unittest
{
Set!int set;
set.insert(8);
auto r1 = set[];
auto r2 = r1.save();
r1.popFront();
assert(r1.empty);
r2.popBack();
assert(r2.empty);
}
// Initial capacity is 0.
@nogc nothrow pure @safe unittest
{
auto set = Set!int(defaultAllocator);
assert(set.capacity == 0);
}
// Capacity is set to a prime.
@nogc nothrow pure @safe unittest
{
auto set = Set!int(8);
assert(set.capacity == 13);
}
// Constructs by reference
@nogc nothrow pure @safe unittest
{
auto set1 = Set!int(7);
auto set2 = Set!int(set1);
assert(set1.length == set2.length);
assert(set1.capacity == set2.capacity);
}
// Constructs by value
@nogc nothrow pure @safe unittest
{
auto set = Set!int(Set!int(7));
assert(set.capacity == 7);
}
// Assigns by reference
@nogc nothrow pure @safe unittest
{
auto set1 = Set!int(7);
Set!int set2;
set1 = set2;
assert(set1.length == set2.length);
assert(set1.capacity == set2.capacity);
}
// Assigns by value
@nogc nothrow pure @safe unittest
{
Set!int set;
set = Set!int(7);
assert(set.capacity == 7);
}
// Postblit copies
@nogc nothrow pure @safe unittest
{
auto set = Set!int(7);
void testFunc(Set!int set)
{
assert(set.capacity == 7);
}
testFunc(set);
}
// Hasher can take argument by ref
@nogc nothrow pure @safe unittest
{
static assert(is(Set!(int, (const ref x) => cast(size_t) x)));
}
// Can have non-copyable elements
@nogc nothrow pure @safe unittest
{
@NonCopyable @Hashable
static struct S
{
mixin StructStub;
}
static assert(is(Set!S));
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1037
source/tanya/conv.d Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,510 +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 2016.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
*/
module tanya.crypto.bit;
/**
* Wrapper that allows bit manipulation on $(D_KEYWORD ubyte[]) array.
*/
struct BitVector
{
protected ubyte[] vector;
/**
* Params:
* array = Array should be manipulated on.
*/
this(inout(ubyte[]) array) inout pure nothrow @safe @nogc
in
{
assert(array.length <= size_t.max / 8);
assert(array !is null);
}
body
{
vector = array;
}
///
unittest
{
ubyte[5] array1 = [234, 3, 252, 10, 18];
ubyte[3] array2 = [65, 13, 173];
auto bits = BitVector(array1);
assert(bits[] is array1);
assert(bits[] !is array2);
bits = BitVector(array2);
assert(bits[] is array2);
}
/**
* Returns: Number of bits in the vector.
*/
@property inout(size_t) length() inout const pure nothrow @safe @nogc
{
return vector.length * 8;
}
/// Ditto.
inout(size_t) opDollar() inout const pure nothrow @safe @nogc
{
return vector.length * 8;
}
///
unittest
{
// [01000001, 00001101, 10101101]
ubyte[3] arr = [65, 13, 173];
auto bits = BitVector(arr);
assert(bits.length == 24);
}
/**
* Params:
* bit = Bit position.
*
* Returns: $(D_KEYWORD true) if the bit on position $(D_PARAM bit) is set,
* $(D_KEYWORD false) if not set.
*/
inout(bool) opIndex(size_t bit) inout const pure nothrow @safe @nogc
in
{
assert(bit / 8 <= vector.length);
}
body
{
return (vector[bit / 8] & (0x80 >> (bit % 8))) != 0;
}
///
unittest
{
// [01000001, 00001101, 10101101]
ubyte[3] arr = [65, 13, 173];
auto bits = BitVector(arr);
assert(!bits[0]);
assert(bits[1]);
assert(bits[7]);
assert(!bits[8]);
assert(!bits[11]);
assert(bits[12]);
assert(bits[20]);
assert(bits[23]);
}
/**
* Returns: Underlying array.
*/
inout(ubyte[]) opIndex() inout pure nothrow @safe @nogc
{
return vector;
}
///
unittest
{
// [01000001, 00001101, 10101101]
ubyte[3] arr = [65, 13, 173];
auto bits = BitVector(arr);
assert(bits[] is arr);
}
/**
* Params:
* value = $(D_KEYWORD true) if the bit should be set,
* $(D_KEYWORD false) if cleared.
* bit = Bit position.
*
* Returns: $(D_PSYMBOL this).
*/
bool opIndexAssign(bool value, size_t bit) pure nothrow @safe @nogc
in
{
assert(bit / 8 <= vector.length);
}
body
{
if (value)
{
vector[bit / 8] |= (0x80 >> (bit % 8));
}
else
{
vector[bit / 8] &= ~(0x80 >> (bit % 8));
}
return value;
}
///
unittest
{
// [01000001, 00001101, 10101101]
ubyte[3] arr = [65, 13, 173];
auto bits = BitVector(arr);
bits[5] = bits[6] = true;
assert(bits[][0] == 71);
bits[14] = true;
bits[15] = false;
assert(bits[][1] == 14);
bits[16] = bits[23] = false;
assert(bits[][2] == 44);
}
/**
* Copies bits from $(D_PARAM vector) into this $(D_PSYMBOL BitVector).
*
* The array that should be assigned, can be smaller (but not larger) than
* the underlying array of this $(D_PSYMBOL BitVector), leading zeros will
* be added in this case to the left.
*
* Params:
* vector = $(D_KEYWORD ubyte[]) array not larger than
* `$(D_PSYMBOL length) / 8`.
*
* Returns: $(D_KEYWORD this).
*/
BitVector opAssign(ubyte[] vector) pure nothrow @safe @nogc
in
{
assert(vector.length <= this.vector.length);
}
body
{
immutable delta = this.vector.length - vector.length;
if (delta > 0)
{
this.vector[0..delta] = 0;
}
this.vector[delta..$] = vector[0..$];
return this;
}
///
unittest
{
ubyte[5] array1 = [234, 3, 252, 10, 18];
ubyte[3] array2 = [65, 13, 173];
auto bits = BitVector(array1);
bits = array2;
assert(bits[][0] == 0);
assert(bits[][1] == 0);
assert(bits[][2] == 65);
assert(bits[][3] == 13);
assert(bits[][4] == 173);
bits = array2[0..2];
assert(bits[][0] == 0);
assert(bits[][1] == 0);
assert(bits[][2] == 0);
assert(bits[][3] == 65);
assert(bits[][4] == 13);
}
/**
* Support for bitwise operations.
*
* Params:
* that = Another bit vector.
*
* Returns: $(D_KEYWORD this).
*/
BitVector opOpAssign(string op)(BitVector that) pure nothrow @safe @nogc
if ((op == "^") || (op == "|") || (op == "&"))
{
return opOpAssign(op)(that.vector);
}
/// Ditto.
BitVector opOpAssign(string op)(ubyte[] that) pure nothrow @safe @nogc
if ((op == "^") || (op == "|") || (op == "&"))
in
{
assert(that.length <= vector.length);
}
body
{
for (int i = cast(int) vector.length - 1; i >= 0; --i)
{
mixin("vector[i] " ~ op ~ "= " ~ "that[i];");
}
immutable delta = vector.length - that.length;
if (delta)
{
static if (op == "&")
{
vector[0..delta] = 0;
}
}
return this;
}
///
unittest
{
// [01000001, 00001101, 10101101]
ubyte[3] array1 = [65, 13, 173];
ubyte[3] array2 = [0b01010010, 0b10111110, 0b10111110];
auto bits = BitVector(array1);
bits |= array2;
assert(bits[][0] == 0b01010011);
assert(bits[][1] == 0b10111111);
assert(bits[][2] == 0b10111111);
bits &= array2;
assert(bits[][0] == array2[0]);
assert(bits[][1] == array2[1]);
assert(bits[][2] == array2[2]);
bits ^= array2;
assert(bits[][0] == 0);
assert(bits[][1] == 0);
assert(bits[][2] == 0);
}
/**
* Support for shift operations.
*
* Params:
* n = Number of bits.
*
* Returns: $(D_KEYWORD this).
*/
BitVector opOpAssign(string op)(in size_t n) pure nothrow @safe @nogc
if ((op == "<<") || (op == ">>"))
{
if (n >= length)
{
vector[0..$] = 0;
}
else if (n != 0)
{
immutable bit = n % 8, step = n / 8;
immutable delta = 8 - bit;
size_t i, j;
static if (op == "<<")
{
for (j = step; j < vector.length - 1; ++i)
{
vector[i] = cast(ubyte)((vector[j] << bit)
| vector[++j] >> delta);
}
vector[i] = cast(ubyte)(vector[j] << bit);
vector[$ - step ..$] = 0;
}
else static if (op == ">>")
{
for (i = vector.length - 1, j = i - step; j > 0; --i)
{
vector[i] = cast(ubyte)((vector[j] >> bit)
| vector[--j] << delta);
}
vector[i] = cast(ubyte)(vector[j] >> bit);
vector[0..step] = 0;
}
}
return this;
}
///
nothrow @safe @nogc unittest
{
ubyte[4] arr = [0b10111110, 0b11110010, 0b01010010, 0b01010011];
auto bits = BitVector(arr);
bits <<= 0;
assert(bits[][0] == 0b10111110 && bits[][1] == 0b11110010
&& bits[][2] == 0b01010010 && bits[][3] == 0b01010011);
bits <<= 2;
assert(bits[][0] == 0b11111011 && bits[][1] == 0b11001001
&& bits[][2] == 0b01001001 && bits[][3] == 0b01001100);
bits <<= 4;
assert(bits[][0] == 0b10111100 && bits[][1] == 0b10010100
&& bits[][2] == 0b10010100 && bits[][3] == 0b11000000);
bits <<= 8;
assert(bits[][0] == 0b10010100 && bits[][1] == 0b10010100
&& bits[][2] == 0b11000000 && bits[][3] == 0b00000000);
bits <<= 7;
assert(bits[][0] == 0b01001010 && bits[][1] == 0b01100000
&& bits[][2] == 0b00000000 && bits[][3] == 0b00000000);
bits <<= 25;
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000000
&& bits[][2] == 0b00000000 && bits[][3] == 0b00000000);
arr = [0b00110011, 0b11001100, 0b11111111, 0b01010101];
bits <<= 24;
assert(bits[][0] == 0b01010101 && bits[][1] == 0b00000000
&& bits[][2] == 0b00000000 && bits[][3] == 0b00000000);
arr[1] = 0b11001100;
arr[2] = 0b11111111;
arr[3] = 0b01010101;
bits <<= 12;
assert(bits[][0] == 0b11001111 && bits[][1] == 0b11110101
&& bits[][2] == 0b01010000 && bits[][3] == 0b00000000);
bits <<= 100;
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000000
&& bits[][2] == 0b00000000 && bits[][3] == 0b00000000);
arr = [0b10111110, 0b11110010, 0b01010010, 0b01010011];
bits >>= 0;
assert(bits[][0] == 0b10111110 && bits[][1] == 0b11110010
&& bits[][2] == 0b01010010 && bits[][3] == 0b01010011);
bits >>= 2;
assert(bits[][0] == 0b00101111 && bits[][1] == 0b10111100
&& bits[][2] == 0b10010100 && bits[][3] == 0b10010100);
bits >>= 4;
assert(bits[][0] == 0b00000010 && bits[][1] == 0b11111011
&& bits[][2] == 0b11001001 && bits[][3] == 0b01001001);
bits >>= 8;
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000010
&& bits[][2] == 0b11111011 && bits[][3] == 0b11001001);
bits >>= 7;
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000000
&& bits[][2] == 0b00000101 && bits[][3] == 0b11110111);
bits >>= 25;
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000000
&& bits[][2] == 0b00000000 && bits[][3] == 0b00000000);
arr = [0b00110011, 0b11001100, 0b11111111, 0b01010101];
bits >>= 24;
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000000
&& bits[][2] == 0b00000000 && bits[][3] == 0b00110011);
arr[1] = 0b11001100;
arr[2] = 0b11111111;
arr[3] = 0b01010101;
bits >>= 12;
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000000
&& bits[][2] == 0b00001100 && bits[][3] == 0b11001111);
bits >>= 100;
assert(bits[][0] == 0b00000000 && bits[][1] == 0b00000000
&& bits[][2] == 0b00000000 && bits[][3] == 0b00000000);
}
/**
* Negates all bits.
*
* Returns: $(D_KEYWORD this).
*/
BitVector opUnary(string op)() pure nothrow @safe @nogc
if (op == "~")
{
foreach (ref b; vector)
{
b = ~b;
}
return this;
}
///
unittest
{
// [01000001, 00001101, 10101101]
ubyte[3] arr = [65, 13, 173];
auto bits = BitVector(arr);
~bits;
assert(bits[][0] == 0b10111110);
assert(bits[][1] == 0b11110010);
assert(bits[][2] == 0b01010010);
}
/**
* Iterates through all bits.
*
* Params:
* dg = $(D_KEYWORD foreach) delegate.
*
* Returns: By $(D_PARAM dg) returned value.
*/
int opApply(int delegate(size_t, bool) dg)
{
int result;
foreach (i, ref v; vector)
{
foreach (c; 0..8)
{
result = dg(i * 8 + c, (v & (0x80 >> c)) != 0);
if (result)
{
return result;
}
}
}
return result;
}
/// Ditto.
int opApply(int delegate(bool) dg)
{
int result;
foreach (ref v; vector)
{
foreach (c; 0..8)
{
result = dg((v & (0x80 >> c)) != 0);
if (result)
{
return result;
}
}
}
return result;
}
///
unittest
{
ubyte[2] arr = [0b01000001, 0b00001101];
auto bits = BitVector(arr);
size_t c;
foreach (i, v; bits)
{
assert(i == c);
if (i == 1 || i == 7 || i == 15 || i == 13 || i == 12)
{
assert(v);
}
else
{
assert(!v);
}
++c;
}
assert(c == 16);
}
}

View File

@ -1,607 +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 2016.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
*/
module tanya.crypto.des;
import tanya.crypto.bit;
import tanya.crypto.symmetric;
/// Initial permutation table.
private immutable ubyte[64] ipTable = [58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7];
/// Final permutation table.
private immutable ubyte[64] fpTable = [40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25];
/// Key permutation table 1.
private immutable ubyte[64] pc1Table = [57, 49, 41, 33, 25, 17, 9, 1,
58, 50, 42, 34, 26, 18, 10, 2,
59, 51, 43, 35, 27, 19, 11, 3,
60, 52, 44, 36, 63, 55, 47, 39,
31, 23, 15, 7, 62, 54, 46, 38,
30, 22, 14, 6, 61, 53, 45, 37,
29, 21, 13, 5, 28, 20, 12, 4];
/// Key permutation table 2.
private immutable ubyte[48] pc2Table = [14, 17, 11, 24, 1, 5, 3, 28,
15, 6, 21, 10, 23, 19, 12, 4,
26, 8, 16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55, 30, 40,
51, 45, 33, 48, 44, 49, 39, 56,
34, 53, 46, 42, 50, 36, 29, 32];
/// Expansion table.
private immutable ubyte[48] expansionTable = [32, 1, 2, 3, 4, 5, 4, 5,
6, 7, 8, 9, 8, 9, 10, 11,
12, 13, 12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21, 20, 21,
22, 23, 24, 25, 24, 25, 26, 27,
28, 29, 28, 29, 30, 31, 32, 1];
/// Final input block permutation.
private immutable ubyte[32] pTable = [16, 7, 20, 21, 29, 12, 28, 17,
1, 15, 23, 26, 5, 18, 31, 10,
2, 8, 24, 14, 32, 27, 3, 9,
19, 13, 30, 6, 22, 11, 4, 25];
/// The (in)famous S-boxes.
private immutable ubyte[64][8] sBox = [[
14, 0, 4, 15, 13, 7, 1, 4, 2, 14, 15, 2, 11, 13, 8, 1,
3, 10, 10, 6, 6, 12, 12, 11, 5, 9, 9, 5, 0, 3, 7, 8,
4, 15, 1, 12, 14, 8, 8, 2, 13, 4, 6, 9, 2, 1, 11, 7,
15, 5, 12, 11, 9, 3, 7, 14, 3, 10, 10, 0, 5, 6, 0, 13,
],[
15, 3, 1, 13, 8, 4, 14, 7, 6, 15, 11, 2, 3, 8, 4, 14,
9, 12, 7, 0, 2, 1, 13, 10, 12, 6, 0, 9, 5, 11, 10, 5,
0, 13, 14, 8, 7, 10, 11, 1, 10, 3, 4, 15, 13, 4, 1, 2,
5, 11, 8, 6, 12, 7, 6, 12, 9, 0, 3, 5, 2, 14, 15, 9,
],[
10, 13, 0, 7, 9, 0, 14, 9, 6, 3, 3, 4, 15, 6, 5, 10,
1, 2, 13, 8, 12, 5, 7, 14, 11, 12, 4, 11, 2, 15, 8, 1,
13, 1, 6, 10, 4, 13, 9, 0, 8, 6, 15, 9, 3, 8, 0, 7,
11, 4, 1, 15, 2, 14, 12, 3, 5, 11, 10, 5, 14, 2, 7, 12,
],[
7, 13, 13, 8, 14, 11, 3, 5, 0, 6, 6, 15, 9, 0, 10, 3,
1, 4, 2, 7, 8, 2, 5, 12, 11, 1, 12, 10, 4, 14, 15, 9,
10, 3, 6, 15, 9, 0, 0, 6, 12, 10, 11, 1, 7, 13, 13, 8,
15, 9, 1, 4, 3, 5, 14, 11, 5, 12, 2, 7, 8, 2, 4, 14,
],[
2, 14, 12, 11, 4, 2, 1, 12, 7, 4, 10, 7, 11, 13, 6, 1,
8, 5, 5, 0, 3, 15, 15, 10, 13, 3, 0, 9, 14, 8, 9, 6,
4, 11, 2, 8, 1, 12, 11, 7, 10, 1, 13, 14, 7, 2, 8, 13,
15, 6, 9, 15, 12, 0, 5, 9, 6, 10, 3, 4, 0, 5, 14, 3,
],[
12, 10, 1, 15, 10, 4, 15, 2, 9, 7, 2, 12, 6, 9, 8, 5,
0, 6, 13, 1, 3, 13, 4, 14, 14, 0, 7, 11, 5, 3, 11, 8,
9, 4, 14, 3, 15, 2, 5, 12, 2, 9, 8, 5, 12, 15, 3, 10,
7, 11, 0, 14, 4, 1, 10, 7, 1, 6, 13, 0, 11, 8, 6, 13,
],[
4, 13, 11, 0, 2, 11, 14, 7, 15, 4, 0, 9, 8, 1, 13, 10,
3, 14, 12, 3, 9, 5, 7, 12, 5, 2, 10, 15, 6, 8, 1, 6,
1, 6, 4, 11, 11, 13, 13, 8, 12, 1, 3, 4, 7, 10, 14, 7,
10, 9, 15, 5, 6, 0, 8, 15, 0, 14, 5, 2, 9, 3, 2, 12,
],[
13, 1, 2, 15, 8, 13, 4, 8, 6, 10, 15, 3, 11, 7, 1, 4,
10, 12, 9, 5, 3, 6, 14, 11, 5, 0, 0, 14, 12, 9, 7, 2,
7, 2, 11, 1, 4, 14, 1, 7, 9, 4, 12, 10, 14, 8, 2, 13,
0, 15, 6, 12, 10, 9, 13, 0, 15, 3, 3, 5, 5, 6, 8, 11,
]];
/**
* Data Encryption Standard.
*
* Params:
* L = Number of keys.
*/
class DES(ushort L = 1) : BlockCipher
if (L == 1)
{
mixin FixedBlockSize!8;
mixin KeyLength!8;
private enum expansionBlockSize = 6;
private enum pc1KeyLength = 7;
private enum subkeyLength = 6;
private ubyte[] key_;
/**
* Params:
* key = Key.
*/
@property void key(ubyte[] key) pure nothrow @safe @nogc
in
{
assert(key.length >= minKeyLength);
assert(key.length <= maxKeyLength);
}
body
{
key_ = key;
}
/**
* Encrypts a block.
*
* Params:
* plain = Plain text, input.
* cipher = Cipher text, output.
*/
void encrypt(in ubyte[] plain, ubyte[] cipher)
nothrow
in
{
assert(plain.length == blockSize);
assert(cipher.length == blockSize);
}
body
{
operateBlock!(Direction.encryption)(plain, cipher);
}
/**
* Decrypts a block.
*
* Params:
* cipher = Cipher text, input.
* plain = Plain text, output.
*/
void decrypt(in ubyte[] cipher, ubyte[] plain)
nothrow
in
{
assert(plain.length == blockSize);
assert(cipher.length == blockSize);
}
body
{
operateBlock!(Direction.decryption)(cipher, plain);
}
private void operateBlock(Direction D)(in ubyte[] source, ref ubyte[] target)
{
ubyte[blockSize_] ipBlock;
ubyte[expansionBlockSize] expansionBlock;
ubyte[4] substitutionBlock;
ubyte[4] pBoxTarget;
ubyte[pc1KeyLength] pc1Key;
ubyte[subkeyLength] subkey;
// Initial permutation
permute(source, ipBlock, ipTable, blockSize);
// Key schedule computation
permute(key_, pc1Key, pc1Table, pc1KeyLength);
// Feistel function
for (ubyte round; round < 16; ++round)
{
auto bitVector = BitVector(expansionBlock);
/* Expansion. This permutation only looks at the first 4 bytes (32
bits of ipBlock); 16 of these are repeated in expansion table.*/
permute(BitVector(ipBlock[4..$]), bitVector, expansionTable, 6);
// Key mixing
static if (D == Direction.encryption)
{
rotateLeft(pc1Key);
if (!(round <= 1 || round == 8 || round == 15))
{
// Rotate twice.
rotateLeft(pc1Key);
}
}
permute(pc1Key, subkey, pc2Table, subkeyLength);
static if (D == Direction.decryption)
{
rotateRight(pc1Key);
if (!(round >= 14 || round == 7 || round == 0))
{
// Rotate twice.
rotateRight(pc1Key);
}
}
bitVector ^= subkey;
// Substitution; copy from updated expansion block to ciphertext block
substitutionBlock[0] = cast(ubyte) (sBox[0][(expansionBlock[0] & 0xfc ) >> 2] << 4);
substitutionBlock[0] |= sBox[1][(expansionBlock[0] & 0x03) << 4 | (expansionBlock[1] & 0xf0) >> 4];
substitutionBlock[1] = cast(ubyte) (sBox[2][(expansionBlock[1] & 0x0f) << 2 | (expansionBlock[2] & 0xc0) >> 6] << 4);
substitutionBlock[1] |= sBox[3][(expansionBlock[2] & 0x3f)];
substitutionBlock[2] = cast(ubyte) (sBox[4][(expansionBlock[3] & 0xfc) >> 2 ] << 4);
substitutionBlock[2] |= sBox[5][(expansionBlock[3] & 0x03) << 4 | (expansionBlock[4] & 0xf0) >> 4];
substitutionBlock[3] = cast(ubyte) (sBox[6][(expansionBlock[4] & 0x0F) << 2 | (expansionBlock[5] & 0xc0) >> 6] << 4);
substitutionBlock[3] |= sBox[7][(expansionBlock[5] & 0x3f)];
// Permutation
bitVector = BitVector(substitutionBlock);
permute(bitVector, pBoxTarget, pTable, blockSize / 2);
// Swap the halves.
substitutionBlock = ipBlock[0..4];
ipBlock[0..4] = ipBlock[4..$];
bitVector ^= pBoxTarget;
ipBlock[4..$] = substitutionBlock;
}
substitutionBlock = ipBlock[0..4];
ipBlock[0..4] = ipBlock[4..$];
ipBlock[4..$] = substitutionBlock;
// Final permutaion (undo initial permuation).
permute(ipBlock, target, fpTable, blockSize);
}
/**
* Performs the left rotation operation on the key.
*
* Params:
* key = The key to rotate.
*/
private void rotateLeft(ref ubyte[7] key) const pure nothrow @safe @nogc
{
immutable carryLeft = (key[0] & 0x80) >> 3;
key[0] = cast(ubyte) ((key[0] << 1) | ((key[1] & 0x80) >> 7));
key[1] = cast(ubyte) ((key[1] << 1) | ((key[2] & 0x80) >> 7));
key[2] = cast(ubyte) ((key[2] << 1) | ((key[3] & 0x80) >> 7));
immutable carryRight = (key[3] & 0x08) >> 3;
key[3] = cast(ubyte) ((((key[3] << 1) | ((key[4] & 0x80) >> 7)) & ~0x10) | carryLeft);
key[4] = cast(ubyte) ((key[4] << 1) | ((key[5] & 0x80) >> 7));
key[5] = cast(ubyte) ((key[5] << 1) | ((key[6] & 0x80) >> 7));
key[6] = cast(ubyte) ((key[6] << 1) | carryRight);
}
/**
* Performs the right rotation operation on the key.
*
* Params:
* key = The key to rotate.
*/
private void rotateRight(ref ubyte[7] key) const pure nothrow @safe @nogc
{
immutable carryRight = (key[6] & 0x01) << 3;
key[6] = cast(ubyte) ((key[6] >> 1) | ((key[5] & 0x01) << 7));
key[5] = cast(ubyte) ((key[5] >> 1) | ((key[4] & 0x01) << 7));
key[4] = cast(ubyte) ((key[4] >> 1) | ((key[3] & 0x01) << 7));
immutable carryLeft = (key[3] & 0x10) << 3;
key[3] = cast(ubyte) ((((key[3] >> 1) | ((key[2] & 0x01) << 7)) & ~0x08) | carryRight);
key[2] = cast(ubyte) ((key[2] >> 1) | ((key[1] & 0x01) << 7));
key[1] = cast(ubyte) ((key[1] >> 1) | ((key[0] & 0x01) << 7));
key[0] = cast(ubyte) ((key[0] >> 1) | carryLeft);
}
private void permute(in ubyte[] source, ubyte[] target, immutable(ubyte[]) permuteTable, size_t length)
const pure nothrow @safe @nogc
{
const sourceVector = const BitVector(source);
auto targetVector = BitVector(target);
permute(sourceVector, targetVector, permuteTable, length);
}
private void permute(in BitVector source, ubyte[] target, immutable(ubyte[]) permuteTable, size_t length)
const pure nothrow @safe @nogc
{
auto targetVector = BitVector(target);
permute(source, targetVector, permuteTable, length);
}
private void permute(in BitVector source, ref BitVector target, immutable(ubyte[]) permuteTable, size_t length)
const pure nothrow @safe @nogc
{
for (uint i; i < length * 8; ++i)
{
target[i] = source[permuteTable[i] - 1];
}
}
}
version (unittest)
{
import std.typecons;
/* Test vectors for DES. Source:
"Validating the Correctness of Hardware
Implementations of the NBS Data Encryption Standard"
NBS Special Publication 500-20, 1980. Appendix B */
// Initial and reverse Permutation and Expansion tests. Encrypt.
ubyte[8][64] desTestVectors1 = [
[0x95, 0xf8, 0xa5, 0xe5, 0xdd, 0x31, 0xd9, 0x00],
[0xdd, 0x7f, 0x12, 0x1c, 0xa5, 0x01, 0x56, 0x19],
[0x2e, 0x86, 0x53, 0x10, 0x4f, 0x38, 0x34, 0xea],
[0x4b, 0xd3, 0x88, 0xff, 0x6c, 0xd8, 0x1d, 0x4f],
[0x20, 0xb9, 0xe7, 0x67, 0xb2, 0xfb, 0x14, 0x56],
[0x55, 0x57, 0x93, 0x80, 0xd7, 0x71, 0x38, 0xef],
[0x6c, 0xc5, 0xde, 0xfa, 0xaf, 0x04, 0x51, 0x2f],
[0x0d, 0x9f, 0x27, 0x9b, 0xa5, 0xd8, 0x72, 0x60],
[0xd9, 0x03, 0x1b, 0x02, 0x71, 0xbd, 0x5a, 0x0a],
[0x42, 0x42, 0x50, 0xb3, 0x7c, 0x3d, 0xd9, 0x51],
[0xb8, 0x06, 0x1b, 0x7e, 0xcd, 0x9a, 0x21, 0xe5],
[0xf1, 0x5d, 0x0f, 0x28, 0x6b, 0x65, 0xbd, 0x28],
[0xad, 0xd0, 0xcc, 0x8d, 0x6e, 0x5d, 0xeb, 0xa1],
[0xe6, 0xd5, 0xf8, 0x27, 0x52, 0xad, 0x63, 0xd1],
[0xec, 0xbf, 0xe3, 0xbd, 0x3f, 0x59, 0x1a, 0x5e],
[0xf3, 0x56, 0x83, 0x43, 0x79, 0xd1, 0x65, 0xcd],
[0x2b, 0x9f, 0x98, 0x2f, 0x20, 0x03, 0x7f, 0xa9],
[0x88, 0x9d, 0xe0, 0x68, 0xa1, 0x6f, 0x0b, 0xe6],
[0xe1, 0x9e, 0x27, 0x5d, 0x84, 0x6a, 0x12, 0x98],
[0x32, 0x9a, 0x8e, 0xd5, 0x23, 0xd7, 0x1a, 0xec],
[0xe7, 0xfc, 0xe2, 0x25, 0x57, 0xd2, 0x3c, 0x97],
[0x12, 0xa9, 0xf5, 0x81, 0x7f, 0xf2, 0xd6, 0x5d],
[0xa4, 0x84, 0xc3, 0xad, 0x38, 0xdc, 0x9c, 0x19],
[0xfb, 0xe0, 0x0a, 0x8a, 0x1e, 0xf8, 0xad, 0x72],
[0x75, 0x0d, 0x07, 0x94, 0x07, 0x52, 0x13, 0x63],
[0x64, 0xfe, 0xed, 0x9c, 0x72, 0x4c, 0x2f, 0xaf],
[0xf0, 0x2b, 0x26, 0x3b, 0x32, 0x8e, 0x2b, 0x60],
[0x9d, 0x64, 0x55, 0x5a, 0x9a, 0x10, 0xb8, 0x52],
[0xd1, 0x06, 0xff, 0x0b, 0xed, 0x52, 0x55, 0xd7],
[0xe1, 0x65, 0x2c, 0x6b, 0x13, 0x8c, 0x64, 0xa5],
[0xe4, 0x28, 0x58, 0x11, 0x86, 0xec, 0x8f, 0x46],
[0xae, 0xb5, 0xf5, 0xed, 0xe2, 0x2d, 0x1a, 0x36],
[0xe9, 0x43, 0xd7, 0x56, 0x8a, 0xec, 0x0c, 0x5c],
[0xdf, 0x98, 0xc8, 0x27, 0x6f, 0x54, 0xb0, 0x4b],
[0xb1, 0x60, 0xe4, 0x68, 0x0f, 0x6c, 0x69, 0x6f],
[0xfa, 0x07, 0x52, 0xb0, 0x7d, 0x9c, 0x4a, 0xb8],
[0xca, 0x3a, 0x2b, 0x03, 0x6d, 0xbc, 0x85, 0x02],
[0x5e, 0x09, 0x05, 0x51, 0x7b, 0xb5, 0x9b, 0xcf],
[0x81, 0x4e, 0xeb, 0x3b, 0x91, 0xd9, 0x07, 0x26],
[0x4d, 0x49, 0xdb, 0x15, 0x32, 0x91, 0x9c, 0x9f],
[0x25, 0xeb, 0x5f, 0xc3, 0xf8, 0xcf, 0x06, 0x21],
[0xab, 0x6a, 0x20, 0xc0, 0x62, 0x0d, 0x1c, 0x6f],
[0x79, 0xe9, 0x0d, 0xbc, 0x98, 0xf9, 0x2c, 0xca],
[0x86, 0x6e, 0xce, 0xdd, 0x80, 0x72, 0xbb, 0x0e],
[0x8b, 0x54, 0x53, 0x6f, 0x2f, 0x3e, 0x64, 0xa8],
[0xea, 0x51, 0xd3, 0x97, 0x55, 0x95, 0xb8, 0x6b],
[0xca, 0xff, 0xc6, 0xac, 0x45, 0x42, 0xde, 0x31],
[0x8d, 0xd4, 0x5a, 0x2d, 0xdf, 0x90, 0x79, 0x6c],
[0x10, 0x29, 0xd5, 0x5e, 0x88, 0x0e, 0xc2, 0xd0],
[0x5d, 0x86, 0xcb, 0x23, 0x63, 0x9d, 0xbe, 0xa9],
[0x1d, 0x1c, 0xa8, 0x53, 0xae, 0x7c, 0x0c, 0x5f],
[0xce, 0x33, 0x23, 0x29, 0x24, 0x8f, 0x32, 0x28],
[0x84, 0x05, 0xd1, 0xab, 0xe2, 0x4f, 0xb9, 0x42],
[0xe6, 0x43, 0xd7, 0x80, 0x90, 0xca, 0x42, 0x07],
[0x48, 0x22, 0x1b, 0x99, 0x37, 0x74, 0x8a, 0x23],
[0xdd, 0x7c, 0x0b, 0xbd, 0x61, 0xfa, 0xfd, 0x54],
[0x2f, 0xbc, 0x29, 0x1a, 0x57, 0x0d, 0xb5, 0xc4],
[0xe0, 0x7c, 0x30, 0xd7, 0xe4, 0xe2, 0x6e, 0x12],
[0x09, 0x53, 0xe2, 0x25, 0x8e, 0x8e, 0x90, 0xa1],
[0x5b, 0x71, 0x1b, 0xc4, 0xce, 0xeb, 0xf2, 0xee],
[0xcc, 0x08, 0x3f, 0x1e, 0x6d, 0x9e, 0x85, 0xf6],
[0xd2, 0xfd, 0x88, 0x67, 0xd5, 0x0d, 0x2d, 0xfe],
[0x06, 0xe7, 0xea, 0x22, 0xce, 0x92, 0x70, 0x8f],
[0x16, 0x6b, 0x40, 0xb4, 0x4a, 0xba, 0x4b, 0xd6],
];
// Key Permutation test. Encrypt.
// Test of right-shifts. Decrypt.
ubyte[8][56] desTestVectors2 = [
[0x95, 0xa8, 0xd7, 0x28, 0x13, 0xda, 0xa9, 0x4d],
[0x0e, 0xec, 0x14, 0x87, 0xdd, 0x8c, 0x26, 0xd5],
[0x7a, 0xd1, 0x6f, 0xfb, 0x79, 0xc4, 0x59, 0x26],
[0xd3, 0x74, 0x62, 0x94, 0xca, 0x6a, 0x6c, 0xf3],
[0x80, 0x9f, 0x5f, 0x87, 0x3c, 0x1f, 0xd7, 0x61],
[0xc0, 0x2f, 0xaf, 0xfe, 0xc9, 0x89, 0xd1, 0xfc],
[0x46, 0x15, 0xaa, 0x1d, 0x33, 0xe7, 0x2f, 0x10],
[0x20, 0x55, 0x12, 0x33, 0x50, 0xc0, 0x08, 0x58],
[0xdf, 0x3b, 0x99, 0xd6, 0x57, 0x73, 0x97, 0xc8],
[0x31, 0xfe, 0x17, 0x36, 0x9b, 0x52, 0x88, 0xc9],
[0xdf, 0xdd, 0x3c, 0xc6, 0x4d, 0xae, 0x16, 0x42],
[0x17, 0x8c, 0x83, 0xce, 0x2b, 0x39, 0x9d, 0x94],
[0x50, 0xf6, 0x36, 0x32, 0x4a, 0x9b, 0x7f, 0x80],
[0xa8, 0x46, 0x8e, 0xe3, 0xbc, 0x18, 0xf0, 0x6d],
[0xa2, 0xdc, 0x9e, 0x92, 0xfd, 0x3c, 0xde, 0x92],
[0xca, 0xc0, 0x9f, 0x79, 0x7d, 0x03, 0x12, 0x87],
[0x90, 0xba, 0x68, 0x0b, 0x22, 0xae, 0xb5, 0x25],
[0xce, 0x7a, 0x24, 0xf3, 0x50, 0xe2, 0x80, 0xb6],
[0x88, 0x2b, 0xff, 0x0a, 0xa0, 0x1a, 0x0b, 0x87],
[0x25, 0x61, 0x02, 0x88, 0x92, 0x45, 0x11, 0xc2],
[0xc7, 0x15, 0x16, 0xc2, 0x9c, 0x75, 0xd1, 0x70],
[0x51, 0x99, 0xc2, 0x9a, 0x52, 0xc9, 0xf0, 0x59],
[0xc2, 0x2f, 0x0a, 0x29, 0x4a, 0x71, 0xf2, 0x9f],
[0xee, 0x37, 0x14, 0x83, 0x71, 0x4c, 0x02, 0xea],
[0xa8, 0x1f, 0xbd, 0x44, 0x8f, 0x9e, 0x52, 0x2f],
[0x4f, 0x64, 0x4c, 0x92, 0xe1, 0x92, 0xdf, 0xed],
[0x1a, 0xfa, 0x9a, 0x66, 0xa6, 0xdf, 0x92, 0xae],
[0xb3, 0xc1, 0xcc, 0x71, 0x5c, 0xb8, 0x79, 0xd8],
[0x19, 0xd0, 0x32, 0xe6, 0x4a, 0xb0, 0xbd, 0x8b],
[0x3c, 0xfa, 0xa7, 0xa7, 0xdc, 0x87, 0x20, 0xdc],
[0xb7, 0x26, 0x5f, 0x7f, 0x44, 0x7a, 0xc6, 0xf3],
[0x9d, 0xb7, 0x3b, 0x3c, 0x0d, 0x16, 0x3f, 0x54],
[0x81, 0x81, 0xb6, 0x5b, 0xab, 0xf4, 0xa9, 0x75],
[0x93, 0xc9, 0xb6, 0x40, 0x42, 0xea, 0xa2, 0x40],
[0x55, 0x70, 0x53, 0x08, 0x29, 0x70, 0x55, 0x92],
[0x86, 0x38, 0x80, 0x9e, 0x87, 0x87, 0x87, 0xa0],
[0x41, 0xb9, 0xa7, 0x9a, 0xf7, 0x9a, 0xc2, 0x08],
[0x7a, 0x9b, 0xe4, 0x2f, 0x20, 0x09, 0xa8, 0x92],
[0x29, 0x03, 0x8d, 0x56, 0xba, 0x6d, 0x27, 0x45],
[0x54, 0x95, 0xc6, 0xab, 0xf1, 0xe5, 0xdf, 0x51],
[0xae, 0x13, 0xdb, 0xd5, 0x61, 0x48, 0x89, 0x33],
[0x02, 0x4d, 0x1f, 0xfa, 0x89, 0x04, 0xe3, 0x89],
[0xd1, 0x39, 0x97, 0x12, 0xf9, 0x9b, 0xf0, 0x2e],
[0x14, 0xc1, 0xd7, 0xc1, 0xcf, 0xfe, 0xc7, 0x9e],
[0x1d, 0xe5, 0x27, 0x9d, 0xae, 0x3b, 0xed, 0x6f],
[0xe9, 0x41, 0xa3, 0x3f, 0x85, 0x50, 0x13, 0x03],
[0xda, 0x99, 0xdb, 0xbc, 0x9a, 0x03, 0xf3, 0x79],
[0xb7, 0xfc, 0x92, 0xf9, 0x1d, 0x8e, 0x92, 0xe9],
[0xae, 0x8e, 0x5c, 0xaa, 0x3c, 0xa0, 0x4e, 0x85],
[0x9c, 0xc6, 0x2d, 0xf4, 0x3b, 0x6e, 0xed, 0x74],
[0xd8, 0x63, 0xdb, 0xb5, 0xc5, 0x9a, 0x91, 0xa0],
[0xa1, 0xab, 0x21, 0x90, 0x54, 0x5b, 0x91, 0xd7],
[0x08, 0x75, 0x04, 0x1e, 0x64, 0xc5, 0x70, 0xf7],
[0x5a, 0x59, 0x45, 0x28, 0xbe, 0xbe, 0xf1, 0xcc],
[0xfc, 0xdb, 0x32, 0x91, 0xde, 0x21, 0xf0, 0xc0],
[0x86, 0x9e, 0xfd, 0x7f, 0x9f, 0x26, 0x5a, 0x09],
];
// Data permutation test. Encrypt.
ubyte[8][2][32] desTestVectors3 = [
[[0x10, 0x46, 0x91, 0x34, 0x89, 0x98, 0x01, 0x31], [0x88, 0xd5, 0x5e, 0x54, 0xf5, 0x4c, 0x97, 0xb4]],
[[0x10, 0x07, 0x10, 0x34, 0x89, 0x98, 0x80, 0x20], [0x0c, 0x0c, 0xc0, 0x0c, 0x83, 0xea, 0x48, 0xfd]],
[[0x10, 0x07, 0x10, 0x34, 0xc8, 0x98, 0x01, 0x20], [0x83, 0xbc, 0x8e, 0xf3, 0xa6, 0x57, 0x01, 0x83]],
[[0x10, 0x46, 0x10, 0x34, 0x89, 0x98, 0x80, 0x20], [0xdf, 0x72, 0x5d, 0xca, 0xd9, 0x4e, 0xa2, 0xe9]],
[[0x10, 0x86, 0x91, 0x15, 0x19, 0x19, 0x01, 0x01], [0xe6, 0x52, 0xb5, 0x3b, 0x55, 0x0b, 0xe8, 0xb0]],
[[0x10, 0x86, 0x91, 0x15, 0x19, 0x58, 0x01, 0x01], [0xaf, 0x52, 0x71, 0x20, 0xc4, 0x85, 0xcb, 0xb0]],
[[0x51, 0x07, 0xb0, 0x15, 0x19, 0x58, 0x01, 0x01], [0x0f, 0x04, 0xce, 0x39, 0x3d, 0xb9, 0x26, 0xd5]],
[[0x10, 0x07, 0xb0, 0x15, 0x19, 0x19, 0x01, 0x01], [0xc9, 0xf0, 0x0f, 0xfc, 0x74, 0x07, 0x90, 0x67]],
[[0x31, 0x07, 0x91, 0x54, 0x98, 0x08, 0x01, 0x01], [0x7c, 0xfd, 0x82, 0xa5, 0x93, 0x25, 0x2b, 0x4e]],
[[0x31, 0x07, 0x91, 0x94, 0x98, 0x08, 0x01, 0x01], [0xcb, 0x49, 0xa2, 0xf9, 0xe9, 0x13, 0x63, 0xe3]],
[[0x10, 0x07, 0x91, 0x15, 0xb9, 0x08, 0x01, 0x40], [0x00, 0xb5, 0x88, 0xbe, 0x70, 0xd2, 0x3f, 0x56]],
[[0x31, 0x07, 0x91, 0x15, 0x98, 0x08, 0x01, 0x40], [0x40, 0x6a, 0x9a, 0x6a, 0xb4, 0x33, 0x99, 0xae]],
[[0x10, 0x07, 0xd0, 0x15, 0x89, 0x98, 0x01, 0x01], [0x6c, 0xb7, 0x73, 0x61, 0x1d, 0xca, 0x9a, 0xda]],
[[0x91, 0x07, 0x91, 0x15, 0x89, 0x98, 0x01, 0x01], [0x67, 0xfd, 0x21, 0xc1, 0x7d, 0xbb, 0x5d, 0x70]],
[[0x91, 0x07, 0xd0, 0x15, 0x89, 0x19, 0x01, 0x01], [0x95, 0x92, 0xcb, 0x41, 0x10, 0x43, 0x07, 0x87]],
[[0x10, 0x07, 0xd0, 0x15, 0x98, 0x98, 0x01, 0x20], [0xa6, 0xb7, 0xff, 0x68, 0xa3, 0x18, 0xdd, 0xd3]],
[[0x10, 0x07, 0x94, 0x04, 0x98, 0x19, 0x01, 0x01], [0x4d, 0x10, 0x21, 0x96, 0xc9, 0x14, 0xca, 0x16]],
[[0x01, 0x07, 0x91, 0x04, 0x91, 0x19, 0x04, 0x01], [0x2d, 0xfa, 0x9f, 0x45, 0x73, 0x59, 0x49, 0x65]],
[[0x01, 0x07, 0x91, 0x04, 0x91, 0x19, 0x01, 0x01], [0xb4, 0x66, 0x04, 0x81, 0x6c, 0x0e, 0x07, 0x74]],
[[0x01, 0x07, 0x94, 0x04, 0x91, 0x19, 0x04, 0x01], [0x6e, 0x7e, 0x62, 0x21, 0xa4, 0xf3, 0x4e, 0x87]],
[[0x19, 0x07, 0x92, 0x10, 0x98, 0x1a, 0x01, 0x01], [0xaa, 0x85, 0xe7, 0x46, 0x43, 0x23, 0x31, 0x99]],
[[0x10, 0x07, 0x91, 0x19, 0x98, 0x19, 0x08, 0x01], [0x2e, 0x5a, 0x19, 0xdb, 0x4d, 0x19, 0x62, 0xd6]],
[[0x10, 0x07, 0x91, 0x19, 0x98, 0x1a, 0x08, 0x01], [0x23, 0xa8, 0x66, 0xa8, 0x09, 0xd3, 0x08, 0x94]],
[[0x10, 0x07, 0x92, 0x10, 0x98, 0x19, 0x01, 0x01], [0xd8, 0x12, 0xd9, 0x61, 0xf0, 0x17, 0xd3, 0x20]],
[[0x10, 0x07, 0x91, 0x15, 0x98, 0x19, 0x01, 0x0b], [0x05, 0x56, 0x05, 0x81, 0x6e, 0x58, 0x60, 0x8f]],
[[0x10, 0x04, 0x80, 0x15, 0x98, 0x19, 0x01, 0x01], [0xab, 0xd8, 0x8e, 0x8b, 0x1b, 0x77, 0x16, 0xf1]],
[[0x10, 0x04, 0x80, 0x15, 0x98, 0x19, 0x01, 0x02], [0x53, 0x7a, 0xc9, 0x5b, 0xe6, 0x9d, 0xa1, 0xe1]],
[[0x10, 0x04, 0x80, 0x15, 0x98, 0x19, 0x01, 0x08], [0xae, 0xd0, 0xf6, 0xae, 0x3c, 0x25, 0xcd, 0xd8]],
[[0x10, 0x02, 0x91, 0x14, 0x98, 0x10, 0x01, 0x04], [0xb3, 0xe3, 0x5a, 0x5e, 0xe5, 0x3e, 0x7b, 0x8d]],
[[0x10, 0x02, 0x91, 0x15, 0x98, 0x19, 0x01, 0x04], [0x61, 0xc7, 0x9c, 0x71, 0x92, 0x1a, 0x2e, 0xf8]],
[[0x10, 0x02, 0x91, 0x15, 0x98, 0x10, 0x02, 0x01], [0xe2, 0xf5, 0x72, 0x8f, 0x09, 0x95, 0x01, 0x3c]],
[[0x10, 0x02, 0x91, 0x16, 0x98, 0x10, 0x01, 0x01], [0x1a, 0xea, 0xc3, 0x9a, 0x61, 0xf0, 0xa4, 0x64]],
];
// S-Box test. Encrypt.
ubyte[8][3][19] desTestVectors4 = [
[[0x7c, 0xa1, 0x10, 0x45, 0x4a, 0x1a, 0x6e, 0x57], [0x01, 0xa1, 0xd6, 0xd0, 0x39, 0x77, 0x67, 0x42],
[0x69, 0x0f, 0x5b, 0x0d, 0x9a, 0x26, 0x93, 0x9b]],
[[0x01, 0x31, 0xd9, 0x61, 0x9d, 0xc1, 0x37, 0x6e], [0x5c, 0xd5, 0x4c, 0xa8, 0x3d, 0xef, 0x57, 0xda],
[0x7a, 0x38, 0x9d, 0x10, 0x35, 0x4b, 0xd2, 0x71]],
[[0x07, 0xa1, 0x13, 0x3e, 0x4a, 0x0b, 0x26, 0x86], [0x02, 0x48, 0xd4, 0x38, 0x06, 0xf6, 0x71, 0x72],
[0x86, 0x8e, 0xbb, 0x51, 0xca, 0xb4, 0x59, 0x9a]],
[[0x38, 0x49, 0x67, 0x4c, 0x26, 0x02, 0x31, 0x9e], [0x51, 0x45, 0x4b, 0x58, 0x2d, 0xdf, 0x44, 0x0a],
[0x71, 0x78, 0x87, 0x6e, 0x01, 0xf1, 0x9b, 0x2a]],
[[0x04, 0xb9, 0x15, 0xba, 0x43, 0xfe, 0xb5, 0xb6], [0x42, 0xfd, 0x44, 0x30, 0x59, 0x57, 0x7f, 0xa2],
[0xaf, 0x37, 0xfb, 0x42, 0x1f, 0x8c, 0x40, 0x95]],
[[0x01, 0x13, 0xb9, 0x70, 0xfd, 0x34, 0xf2, 0xce], [0x05, 0x9b, 0x5e, 0x08, 0x51, 0xcf, 0x14, 0x3a],
[0x86, 0xa5, 0x60, 0xf1, 0x0e, 0xc6, 0xd8, 0x5b]],
[[0x01, 0x70, 0xf1, 0x75, 0x46, 0x8f, 0xb5, 0xe6], [0x07, 0x56, 0xd8, 0xe0, 0x77, 0x47, 0x61, 0xd2],
[0x0c, 0xd3, 0xda, 0x02, 0x00, 0x21, 0xdc, 0x09]],
[[0x43, 0x29, 0x7f, 0xad, 0x38, 0xe3, 0x73, 0xfe], [0x76, 0x25, 0x14, 0xb8, 0x29, 0xbf, 0x48, 0x6a],
[0xea, 0x67, 0x6b, 0x2c, 0xb7, 0xdb, 0x2b, 0x7a]],
[[0x07, 0xa7, 0x13, 0x70, 0x45, 0xda, 0x2a, 0x16], [0x3b, 0xdd, 0x11, 0x90, 0x49, 0x37, 0x28, 0x02],
[0xdf, 0xd6, 0x4a, 0x81, 0x5c, 0xaf, 0x1a, 0x0f]],
[[0x04, 0x68, 0x91, 0x04, 0xc2, 0xfd, 0x3b, 0x2f], [0x26, 0x95, 0x5f, 0x68, 0x35, 0xaf, 0x60, 0x9a],
[0x5c, 0x51, 0x3c, 0x9c, 0x48, 0x86, 0xc0, 0x88]],
[[0x37, 0xd0, 0x6b, 0xb5, 0x16, 0xcb, 0x75, 0x46], [0x16, 0x4d, 0x5e, 0x40, 0x4f, 0x27, 0x52, 0x32],
[0x0a, 0x2a, 0xee, 0xae, 0x3f, 0xf4, 0xab, 0x77]],
[[0x1f, 0x08, 0x26, 0x0d, 0x1a, 0xc2, 0x46, 0x5e], [0x6b, 0x05, 0x6e, 0x18, 0x75, 0x9f, 0x5c, 0xca],
[0xef, 0x1b, 0xf0, 0x3e, 0x5d, 0xfa, 0x57, 0x5a]],
[[0x58, 0x40, 0x23, 0x64, 0x1a, 0xba, 0x61, 0x76], [0x00, 0x4b, 0xd6, 0xef, 0x09, 0x17, 0x60, 0x62],
[0x88, 0xbf, 0x0d, 0xb6, 0xd7, 0x0d, 0xee, 0x56]],
[[0x02, 0x58, 0x16, 0x16, 0x46, 0x29, 0xb0, 0x07], [0x48, 0x0d, 0x39, 0x00, 0x6e, 0xe7, 0x62, 0xf2],
[0xa1, 0xf9, 0x91, 0x55, 0x41, 0x02, 0x0b, 0x56]],
[[0x49, 0x79, 0x3e, 0xbc, 0x79, 0xb3, 0x25, 0x8f], [0x43, 0x75, 0x40, 0xc8, 0x69, 0x8f, 0x3c, 0xfa],
[0x6f, 0xbf, 0x1c, 0xaf, 0xcf, 0xfd, 0x05, 0x56]],
[[0x4f, 0xb0, 0x5e, 0x15, 0x15, 0xab, 0x73, 0xa7], [0x07, 0x2d, 0x43, 0xa0, 0x77, 0x07, 0x52, 0x92],
[0x2f, 0x22, 0xe4, 0x9b, 0xab, 0x7c, 0xa1, 0xac]],
[[0x49, 0xe9, 0x5d, 0x6d, 0x4c, 0xa2, 0x29, 0xbf], [0x02, 0xfe, 0x55, 0x77, 0x81, 0x17, 0xf1, 0x2a],
[0x5a, 0x6b, 0x61, 0x2c, 0xc2, 0x6c, 0xce, 0x4a]],
[[0x01, 0x83, 0x10, 0xdc, 0x40, 0x9b, 0x26, 0xd6], [0x1d, 0x9d, 0x5c, 0x50, 0x18, 0xf7, 0x28, 0xc2],
[0x5f, 0x4c, 0x03, 0x8e, 0xd1, 0x2b, 0x2e, 0x41]],
[[0x1c, 0x58, 0x7f, 0x1c, 0x13, 0x92, 0x4f, 0xef], [0x30, 0x55, 0x32, 0x28, 0x6d, 0x6f, 0x29, 0x5a],
[0x63, 0xfa, 0xc0, 0xd0, 0x34, 0xd9, 0xf7, 0x93]],
];
}
///
unittest
{
auto des = scoped!(DES!1);
ubyte[8] key = [0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01];
ubyte[8] plain = [0x80, 0, 0, 0, 0, 0, 0, 0];
ubyte[8] cipher;
des.key = key;
foreach (ubyte i; 0..64)
{
if (i != 0)
{
plain[i / 8] = i % 8 ? plain[i / 8] >> 1 : 0x80;
if (i % 8 == 0)
{
plain[i / 8 - 1] = 0;
}
}
// Initial Permutation and Expansion test.
des.encrypt(plain, cipher);
assert(cipher == desTestVectors1[i]);
// Inverse Permutation and Expansion test.
des.encrypt(cipher, cipher);
assert(cipher == plain);
}
plain[0..$] = 0;
foreach (ubyte i; 0..56)
{
key[i / 7] = i % 7 ? key[i / 7] >> 1 : 0x80;
if (i % 7 == 0 && i != 0)
{
key[i / 7 - 1] = 0x01;
}
des.key = key;
// Initial Permutation and Expansion test.
des.encrypt(plain, cipher);
assert(cipher == desTestVectors2[i]);
// Test of right-shifts in Decryption.
des.decrypt(desTestVectors2[i], cipher);
assert(cipher == plain);
}
// Data permutation test.
plain[0..$] = 0;
foreach (i; desTestVectors3)
{
des.key = i[0];
des.encrypt(plain, cipher);
assert(cipher == i[1]);
}
// S-Box test.
foreach (i; desTestVectors4)
{
des.key = i[0];
des.encrypt(i[1], cipher);
assert(cipher == i[2]);
}
}

View File

@ -1,279 +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/. */
/**
* Block cipher modes of operation.
*
* Copyright: Eugene Wissner 2016.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
*/
module tanya.crypto.mode;
import tanya.memory;
import std.algorithm.iteration;
import std.typecons;
/**
* Supported padding mode.
*
* See_Also:
* $(D_PSYMBOL pad)
*/
enum PaddingMode
{
zero,
pkcs7,
ansiX923,
}
/**
* Params:
* input = Sequence that should be padded.
* mode = Padding mode.
* blockSize = Block size.
* allocator = Allocator was used to allocate $(D_PARAM input).
*
* Returns: The function modifies the initial array and returns it.
*
* See_Also:
* $(D_PSYMBOL PaddingMode)
*/
ubyte[] pad(ref ubyte[] input,
in PaddingMode mode,
in ushort blockSize,
shared Allocator allocator = defaultAllocator)
in
{
assert(blockSize > 0 && blockSize <= 256);
assert(blockSize % 64 == 0);
assert(input.length > 0);
}
body
{
immutable rest = cast(ubyte) input.length % blockSize;
immutable size_t lastBlock = input.length - (rest > 0 ? rest : blockSize);
immutable needed = cast(ubyte) (rest > 0 ? blockSize - rest : 0);
final switch (mode) with (PaddingMode)
{
case zero:
allocator.resizeArray(input, input.length + needed);
break;
case pkcs7:
if (needed)
{
allocator.resizeArray(input, input.length + needed);
input[input.length - needed ..$].each!((ref e) => e = needed);
}
else
{
allocator.resizeArray(input, input.length + blockSize);
}
break;
case ansiX923:
allocator.resizeArray(input, input.length + (needed ? needed : blockSize));
input[$ - 1] = needed;
break;
}
return input;
}
///
unittest
{
{ // Zeros
auto input = defaultAllocator.makeArray!ubyte(50);
pad(input, PaddingMode.zero, 64);
assert(input.length == 64);
pad(input, PaddingMode.zero, 64);
assert(input.length == 64);
assert(input[63] == 0);
defaultAllocator.dispose(input);
}
{ // PKCS#7
auto input = defaultAllocator.makeArray!ubyte(50);
for (ubyte i; i < 40; ++i)
{
input[i] = i;
}
pad(input, PaddingMode.pkcs7, 64);
assert(input.length == 64);
for (ubyte i; i < 64; ++i)
{
if (i >= 40 && i < 50)
{
assert(input[i] == 0);
}
else if (i >= 50)
{
assert(input[i] == 14);
}
else
{
assert(input[i] == i);
}
}
pad(input, PaddingMode.pkcs7, 64);
assert(input.length == 128);
for (ubyte i; i < 128; ++i)
{
if (i >= 64 || (i >= 40 && i < 50))
{
assert(input[i] == 0);
}
else if (i >= 50 && i < 64)
{
assert(input[i] == 14);
}
else
{
assert(input[i] == i);
}
}
defaultAllocator.dispose(input);
}
{ // ANSI X.923
auto input = defaultAllocator.makeArray!ubyte(50);
for (ubyte i; i < 40; ++i)
{
input[i] = i;
}
pad(input, PaddingMode.ansiX923, 64);
assert(input.length == 64);
for (ubyte i; i < 64; ++i)
{
if (i < 40)
{
assert(input[i] == i);
}
else if (i == 63)
{
assert(input[i] == 14);
}
else
{
assert(input[i] == 0);
}
}
pad(input, PaddingMode.pkcs7, 64);
assert(input.length == 128);
for (ubyte i = 0; i < 128; ++i)
{
if (i < 40)
{
assert(input[i] == i);
}
else if (i == 63)
{
assert(input[i] == 14);
}
else
{
assert(input[i] == 0);
}
}
defaultAllocator.dispose(input);
}
}
/**
* Params:
* input = Sequence that should be padded.
* mode = Padding mode.
* blockSize = Block size.
* allocator = Allocator was used to allocate $(D_PARAM input).
*
* Returns: The function modifies the initial array and returns it.
*
* See_Also:
* $(D_PSYMBOL pad)
*/
ref ubyte[] unpad(ref ubyte[] input,
in PaddingMode mode,
in ushort blockSize,
shared Allocator allocator = defaultAllocator)
in
{
assert(input.length != 0);
assert(input.length % 64 == 0);
}
body
{
final switch (mode) with (PaddingMode)
{
case zero:
break;
case pkcs7:
case ansiX923:
immutable last = input[$ - 1];
allocator.resizeArray(input, input.length - (last ? last : blockSize));
break;
}
return input;
}
///
unittest
{
{ // Zeros
auto input = defaultAllocator.makeArray!ubyte(50);
auto inputDup = defaultAllocator.makeArray!ubyte(50);
pad(input, PaddingMode.zero, 64);
pad(inputDup, PaddingMode.zero, 64);
unpad(input, PaddingMode.zero, 64);
assert(input == inputDup);
defaultAllocator.dispose(input);
defaultAllocator.dispose(inputDup);
}
{ // PKCS#7
auto input = defaultAllocator.makeArray!ubyte(50);
auto inputDup = defaultAllocator.makeArray!ubyte(50);
for (ubyte i; i < 40; ++i)
{
input[i] = i;
inputDup[i] = i;
}
pad(input, PaddingMode.pkcs7, 64);
unpad(input, PaddingMode.pkcs7, 64);
assert(input == inputDup);
defaultAllocator.dispose(input);
defaultAllocator.dispose(inputDup);
}
{ // ANSI X.923
auto input = defaultAllocator.makeArray!ubyte(50);
auto inputDup = defaultAllocator.makeArray!ubyte(50);
for (ubyte i; i < 40; ++i)
{
input[i] = i;
inputDup[i] = i;
}
pad(input, PaddingMode.pkcs7, 64);
unpad(input, PaddingMode.pkcs7, 64);
assert(input == inputDup);
defaultAllocator.dispose(input);
defaultAllocator.dispose(inputDup);
}
}

View File

@ -1,177 +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/. */
/**
* Interfaces for implementing secret key algorithms.
*
* Copyright: Eugene Wissner 2016.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
*/
module tanya.crypto.symmetric;
/**
* Implemented by secret key algorithms.
*/
interface SymmetricCipher
{
/**
* Returns: Key length.
*/
@property inout(uint) keyLength() inout const pure nothrow @safe @nogc;
/**
* Returns: Minimum key length.
*/
@property inout(uint) minKeyLength() inout const pure nothrow @safe @nogc;
/**
* Returns: Maximum key length.
*/
@property inout(uint) maxKeyLength() inout const pure nothrow @safe @nogc;
/// Cipher direction.
protected enum Direction : ushort
{
encryption,
decryption,
}
/**
* Params:
* key = Key.
*/
@property void key(ubyte[] key) pure nothrow @safe @nogc
in
{
assert(key.length >= minKeyLength);
assert(key.length <= maxKeyLength);
}
}
/**
* Implemented by block ciphers.
*/
interface BlockCipher : SymmetricCipher
{
/**
* Returns: Block size.
*/
@property inout(uint) blockSize() inout const pure nothrow @safe @nogc;
/**
* Encrypts a block.
*
* Params:
* plain = Plain text, input.
* cipher = Cipher text, output.
*/
void encrypt(in ubyte[] plain, ubyte[] cipher)
in
{
assert(plain.length == blockSize);
assert(cipher.length == blockSize);
}
/**
* Decrypts a block.
*
* Params:
* cipher = Cipher text, input.
* plain = Plain text, output.
*/
void decrypt(in ubyte[] cipher, ubyte[] plain)
in
{
assert(plain.length == blockSize);
assert(cipher.length == blockSize);
}
}
/**
* Mixed in by algorithms with fixed block size.
*
* Params:
* N = Block size.
*/
mixin template FixedBlockSize(uint N)
if (N != 0)
{
private enum uint blockSize_ = N;
/**
* Returns: Fixed block size.
*/
final @property inout(uint) blockSize() inout const pure nothrow @safe @nogc
{
return blockSize_;
}
}
/**
* Mixed in by symmetric algorithms.
* If $(D_PARAM Min) equals $(D_PARAM Max) fixed key length is assumed.
*
* Params:
* Min = Minimum key length.
* Max = Maximum key length.
*/
mixin template KeyLength(uint Min, uint Max = Min)
if (Min != 0 && Max != 0)
{
static if (Min == Max)
{
private enum uint keyLength_ = Min;
/**
* Returns: Key length.
*/
final @property inout(uint) keyLength() inout const pure nothrow @safe @nogc
{
return keyLength_;
}
/**
* Returns: Minimum key length.
*/
final @property inout(uint) minKeyLength() inout const pure nothrow @safe @nogc
{
return keyLength_;
}
/**
* Returns: Maximum key length.
*/
final @property inout(uint) maxKeyLength() inout const pure nothrow @safe @nogc
{
return keyLength_;
}
}
else static if (Min < Max)
{
private enum uint minKeyLength_ = Min;
private enum uint maxKeyLength_ = Max;
/**
* Returns: Minimum key length.
*/
final @property inout(uint) minKeyLength() inout const pure nothrow @safe @nogc
{
return minKeyLength_;
}
/**
* Returns: Maximum key length.
*/
final @property inout(uint) maxKeyLength() inout const pure nothrow @safe @nogc
{
return maxKeyLength_;
}
}
else
{
static assert(false, "Max should be larger or equal to Min");
}
}

View File

@ -0,0 +1,501 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Functions operating on ASCII characters.
*
* ASCII is $(B A)merican $(B S)tandard $(B C)ode for $(B I)nformation
* $(B I)nterchange.
*
* Copyright: Eugene Wissner 2017-2018.
* 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,
* tanya/encoding/ascii.d)
*/
module tanya.encoding.ascii;
import tanya.meta.trait;
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.
immutable string letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
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).
*/
immutable string whitespace = "\t\n\v\f\r ";
/// Letter case specifier.
enum LetterCase : bool
{
upper, /// Uppercase.
lower, /// Lowercase.
}
/**
* Checks for an uppecase alphabetic character.
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM c) is an uppercase alphabetic
* character, $(D_KEYWORD false) otherwise.
*/
bool isUpper(C)(C c)
if (isSomeChar!C)
{
return (c >= 'A') && (c <= 'Z');
}
///
@nogc nothrow pure @safe unittest
{
assert(isUpper('A'));
assert(isUpper('Z'));
assert(isUpper('L'));
assert(!isUpper('a'));
assert(!isUpper('!'));
}
/**
* Checks for a lowercase alphabetic character.
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM c) is a lowercase alphabetic
* character, $(D_KEYWORD false) otherwise.
*/
bool isLower(C)(C c)
if (isSomeChar!C)
{
return (c >= 'a') && (c <= 'z');
}
///
@nogc nothrow pure @safe unittest
{
assert(isLower('a'));
assert(isLower('z'));
assert(isLower('l'));
assert(!isLower('A'));
assert(!isLower('!'));
}
/**
* Checks for an alphabetic character (upper- or lowercase).
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM c) is an alphabetic character,
* $(D_KEYWORD false) otherwise.
*/
bool isAlpha(C)(C c)
if (isSomeChar!C)
{
return isUpper(c) || isLower(c);
}
///
@nogc nothrow pure @safe unittest
{
assert(isAlpha('A'));
assert(isAlpha('Z'));
assert(isAlpha('L'));
assert(isAlpha('a'));
assert(isAlpha('z'));
assert(isAlpha('l'));
assert(!isAlpha('!'));
}
/**
* Checks for a digit.
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM c) is a digit,
* $(D_KEYWORD false) otherwise.
*/
bool isDigit(C)(C c)
if (isSomeChar!C)
{
return (c >= '0') && (c <= '9');
}
///
@nogc nothrow pure @safe unittest
{
assert(isDigit('0'));
assert(isDigit('1'));
assert(isDigit('2'));
assert(isDigit('3'));
assert(isDigit('4'));
assert(isDigit('5'));
assert(isDigit('6'));
assert(isDigit('7'));
assert(isDigit('8'));
assert(isDigit('9'));
assert(!isDigit('a'));
assert(!isDigit('!'));
}
/**
* Checks for an alphabetic character (upper- or lowercase) or a digit.
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM c) is an alphabetic character or a
* digit, $(D_KEYWORD false) otherwise.
*/
bool isAlphaNum(C)(C c)
if (isSomeChar!C)
{
return isAlpha(c) || isDigit(c);
}
///
@nogc nothrow pure @safe unittest
{
assert(isAlphaNum('0'));
assert(isAlphaNum('1'));
assert(isAlphaNum('9'));
assert(isAlphaNum('A'));
assert(isAlphaNum('Z'));
assert(isAlphaNum('L'));
assert(isAlphaNum('a'));
assert(isAlphaNum('z'));
assert(isAlphaNum('l'));
assert(!isAlphaNum('!'));
}
/**
* Checks for a 7-bit ASCII character.
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM c) is an ASCII character,
* $(D_KEYWORD false) otherwise.
*/
bool isASCII(C)(C c)
if (isSomeChar!C)
{
return c < 128;
}
///
@nogc nothrow pure @safe unittest
{
assert(isASCII('0'));
assert(isASCII('L'));
assert(isASCII('l'));
assert(isASCII('!'));
assert(!isASCII('©'));
assert(!isASCII('§'));
assert(!isASCII(char.init)); // 0xFF
assert(!isASCII(wchar.init)); // 0xFFFF
assert(!isASCII(dchar.init)); // 0xFFFF
}
/**
* Checks for a control character.
*
* Control characters are non-printable characters. Their ASCII codes are those
* between 0x00 (NUL) and 0x1f (US), and 0x7f (DEL).
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM c) is a control character,
* $(D_KEYWORD false) otherwise.
*
* See_Also: $(D_PSYMBOL isPrintable), $(D_PSYMBOL isGraphical).
*/
bool isControl(C)(C c)
if (isSomeChar!C)
{
return (c <= 0x1f) || (c == 0x7f);
}
///
@nogc nothrow pure @safe unittest
{
assert(isControl('\t'));
assert(isControl('\0'));
assert(isControl('\u007f'));
assert(!isControl(' '));
assert(!isControl('a'));
assert(!isControl(char.init)); // 0xFF
assert(!isControl(wchar.init)); // 0xFFFF
}
/**
* Checks for a whitespace character.
*
* Whitespace characters are:
*
* $(UL
* $(LI Whitespace)
* $(LI Horizontal Tab (HT))
* $(LI Line Feed (LF))
* $(LI Carriage Return (CR))
* $(LI Vertical Tab (VT))
* $(LI Form Feed (FF))
* )
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM c) is a whitespace character,
* $(D_KEYWORD false) otherwise.
*
* See_Also: $(D_PSYMBOL whitespace).
*/
bool isWhite(C)(C c)
if (isSomeChar!C)
{
return ((c >= 0x09) && (c <= 0x0d)) || (c == 0x20);
}
///
@nogc nothrow pure @safe unittest
{
assert(isWhite('\t'));
assert(isWhite('\n'));
assert(isWhite('\v'));
assert(isWhite('\f'));
assert(isWhite('\r'));
assert(isWhite(' '));
}
/**
* Checks for a graphical character.
*
* Graphical characters are printable characters but whitespace characters.
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM c) is a control character,
* $(D_KEYWORD false) otherwise.
*
* See_Also: $(D_PSYMBOL isControl), $(D_PSYMBOL isWhite).
*/
bool isGraphical(C)(C c)
if (isSomeChar!C)
{
return (c > 0x20) && (c < 0x7f);
}
///
@nogc nothrow pure @safe unittest
{
assert(isGraphical('a'));
assert(isGraphical('0'));
assert(!isGraphical('\u007f'));
assert(!isGraphical('§'));
assert(!isGraphical('\n'));
assert(!isGraphical(' '));
}
/**
* Checks for a printable character.
*
* This is the opposite of a control character.
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM c) is a control character,
* $(D_KEYWORD false) otherwise.
*
* See_Also: $(D_PSYMBOL isControl).
*/
bool isPrintable(C)(C c)
if (isSomeChar!C)
{
return (c >= 0x20) && (c < 0x7f);
}
///
@nogc nothrow pure @safe unittest
{
assert(isPrintable('a'));
assert(isPrintable('0'));
assert(!isPrintable('\u007f'));
assert(!isPrintable('§'));
assert(!isPrintable('\n'));
assert(isPrintable(' '));
}
/**
* Checks for a hexadecimal digit.
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM c) is a hexadecimal digit,
* $(D_KEYWORD false) otherwise.
*/
bool isHexDigit(C)(C c)
if (isSomeChar!C)
{
return ((c >= '0') && (c <= '9'))
|| ((c >= 'a') && (c <= 'f'))
|| ((c >= 'A') && (c <= 'F'));
}
///
@nogc nothrow pure @safe unittest
{
assert(isHexDigit('0'));
assert(isHexDigit('1'));
assert(isHexDigit('8'));
assert(isHexDigit('9'));
assert(isHexDigit('A'));
assert(isHexDigit('F'));
assert(!isHexDigit('G'));
assert(isHexDigit('a'));
assert(isHexDigit('f'));
assert(!isHexDigit('g'));
}
/**
* Checks for an octal character.
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM c) is an octal character,
* $(D_KEYWORD false) otherwise.
*/
bool isOctalDigit(C)(C c)
if (isSomeChar!C)
{
return (c >= '0') && (c <= '7');
}
///
@nogc nothrow pure @safe unittest
{
assert(isOctalDigit('0'));
assert(isOctalDigit('1'));
assert(isOctalDigit('2'));
assert(isOctalDigit('3'));
assert(isOctalDigit('4'));
assert(isOctalDigit('5'));
assert(isOctalDigit('6'));
assert(isOctalDigit('7'));
assert(!isOctalDigit('8'));
}
/**
* Checks for a octal character.
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM c) is a octal character,
* $(D_KEYWORD false) otherwise.
*/
bool isPunctuation(C)(C c)
if (isSomeChar!C)
{
return ((c >= 0x21) && (c <= 0x2f))
|| ((c >= 0x3a) && (c <= 0x40))
|| ((c >= 0x5b) && (c <= 0x60))
|| ((c >= 0x7b) && (c <= 0x7e));
}
///
@nogc nothrow pure @safe unittest
{
assert(isPunctuation('!'));
assert(isPunctuation(':'));
assert(isPunctuation('\\'));
assert(isPunctuation('|'));
assert(!isPunctuation('0'));
assert(!isPunctuation(' '));
}
/**
* Converts $(D_PARAM c) to uppercase.
*
* If $(D_PARAM c) is not a lowercase character, $(D_PARAM c) is returned
* unchanged.
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: The lowercase of $(D_PARAM c) if available, just $(D_PARAM c)
* otherwise.
*/
C toUpper(C)(C c)
if (isSomeChar!C)
{
return isLower(c) ? (cast(C) (c - 32)) : c;
}
///
@nogc nothrow pure @safe unittest
{
assert(toUpper('a') == 'A');
assert(toUpper('A') == 'A');
assert(toUpper('!') == '!');
}
/**
* Converts $(D_PARAM c) to lowercase.
*
* If $(D_PARAM c) is not an uppercase character, $(D_PARAM c) is returned
* unchanged.
*
* Params:
* C = Some character type.
* c = Some character.
*
* Returns: The uppercase of $(D_PARAM c) if available, just $(D_PARAM c)
* otherwise.
*/
C toLower(C)(C c)
if (isSomeChar!C)
{
return isUpper(c) ? (cast(C) (c + 32)) : c;
}
///
@nogc nothrow pure @safe unittest
{
assert(toLower('A') == 'a');
assert(toLower('a') == 'a');
assert(toLower('!') == '!');
}

View File

@ -0,0 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This package provides tools to work with text encodings.
*
* Copyright: Eugene Wissner 2017-2018.
* 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,
* tanya/encoding/package.d)
*/
module tanya.encoding;
public import tanya.encoding.ascii;

66
source/tanya/exception.d Normal file
View File

@ -0,0 +1,66 @@
/* 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-2018.
* 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);
}

2731
source/tanya/format.d Normal file

File diff suppressed because it is too large Load Diff

78
source/tanya/functional.d Normal file
View File

@ -0,0 +1,78 @@
/* 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/. */
/**
* Functions that manipulate other functions and their argument lists.
*
* Copyright: Eugene Wissner 2018.
* 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/functional.d,
* tanya/functional.d)
*/
module tanya.functional;
import tanya.algorithm.mutation;
import tanya.meta.metafunction;
/**
* 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));
}
})));
}

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

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

View File

@ -3,14 +3,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Copyright: Eugene Wissner 2016. * Copyright: Eugene Wissner 2018.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) * 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.crypto; module tanya.hash;
public import tanya.crypto.bit; public import tanya.hash.lookup;
public import tanya.crypto.des;
public import tanya.crypto.mode;
public import tanya.crypto.symmetric;

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -3,10 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Copyright: Eugene Wissner 2016. * 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-2018.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * 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; module tanya.memory.allocator;
@ -15,113 +22,60 @@ module tanya.memory.allocator;
*/ */
interface Allocator interface Allocator
{ {
/** /**
* Returns: Alignment offered. * Returns: Alignment offered.
*/ */
@property uint alignment() const shared pure nothrow @safe @nogc; @property uint alignment() const shared pure nothrow @safe @nogc;
/** /**
* Allocates $(D_PARAM size) bytes of memory. * Allocates $(D_PARAM size) bytes of memory.
* *
* Params: * Params:
* size = Amount of memory to allocate. * size = Amount of memory to allocate.
* *
* Returns: Pointer to the new allocated memory. * Returns: Pointer to the new allocated memory.
*/ */
void[] allocate(in size_t size) shared nothrow @nogc; void[] allocate(size_t size) shared pure nothrow @nogc;
/** /**
* Deallocates a memory block. * Deallocates a memory block.
* *
* Params: * Params:
* p = A pointer to the memory block to be freed. * p = A pointer to the memory block to be freed.
* *
* Returns: Whether the deallocation was successful. * Returns: Whether the deallocation was successful.
*/ */
bool deallocate(void[] p) shared nothrow @nogc; bool deallocate(void[] p) shared pure nothrow @nogc;
/** /**
* Increases or decreases the size of a memory block. * Increases or decreases the size of a memory block.
* *
* Params: * Params:
* p = A pointer to the memory block. * p = A pointer to the memory block.
* size = Size of the reallocated block. * size = Size of the reallocated block.
* *
* Returns: Pointer to the allocated memory. * Returns: Pointer to the allocated memory.
*/ */
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc; bool reallocate(ref void[] p, size_t size) shared pure nothrow @nogc;
/** /**
* Expands a memory block in place. * Reallocates a memory block in place if possible or returns
* * $(D_KEYWORD false). This function cannot be used to allocate or
* Params: * deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
* p = A pointer to the memory block. * $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
* size = Size of the reallocated block. *
* * Params:
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise. * p = A pointer to the memory block.
*/ * size = Size of the reallocated block.
bool expand(ref void[] p, in size_t size) shared nothrow @nogc; *
* 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)
* The mixin generates common methods for classes and structs using
* allocators. It provides a protected member, constructor and a read-only property,
* that checks if an allocator was already set and sets it to the default
* one, if not (useful for structs which don't have a default constructor).
*/
mixin template DefaultAllocator()
{ {
/// Allocator. alias GetPureInstance = shared(T) function()
protected shared Allocator allocator_; pure nothrow @nogc;
/**
* Params:
* allocator = The allocator should be used.
*/
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
{
this.allocator_ = allocator;
}
/**
* This property checks if the allocator was set in the constructor
* and sets it to the default one, if not.
*
* Returns: Used allocator.
*
* Postcondition: $(D_INLINECODE allocator_ !is null)
*/
protected @property shared(Allocator) allocator() nothrow @safe @nogc
out (allocator)
{
assert(allocator !is null);
}
body
{
if (allocator_ is null)
{
allocator_ = defaultAllocator;
}
return allocator_;
}
/// Ditto.
@property shared(Allocator) allocator() const nothrow @trusted @nogc
out (allocator)
{
assert(allocator !is null);
}
body
{
if (allocator_ is null)
{
return defaultAllocator;
}
return cast(shared Allocator) allocator_;
}
} }

View File

@ -0,0 +1,232 @@
/* 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/. */
/**
* Allocator based on $(D_PSYMBOL malloc), $(D_PSYMBOL realloc) and $(D_PSYMBOL free).
*
* Copyright: Eugene Wissner 2017-2018.
* 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,
* tanya/memory/mallocator.d)
*/
module tanya.memory.mallocator;
version (TanyaNative)
{
}
else:
import core.stdc.stdlib;
import tanya.memory.allocator;
/**
* Wrapper for $(D_PSYMBOL malloc)/$(D_PSYMBOL realloc)/$(D_PSYMBOL free) from
* the C standard library.
*/
final class Mallocator : Allocator
{
private alias MallocType = extern (C) void* function(size_t)
@nogc nothrow pure @system;
private alias FreeType = extern (C) void function(void*)
@nogc nothrow pure @system;
private alias ReallocType = extern (C) void* function(void*, size_t)
@nogc nothrow pure @system;
/**
* Allocates $(D_PARAM size) bytes of memory.
*
* Params:
* size = Amount of memory to allocate.
*
* Returns: The pointer to the new allocated memory.
*/
void[] allocate(size_t size) @nogc nothrow pure shared @system
{
if (size == 0)
{
return null;
}
auto p = (cast(MallocType) &malloc)(size + psize);
return p is null ? null : p[psize .. psize + size];
}
///
@nogc nothrow pure @system unittest
{
auto p = Mallocator.instance.allocate(20);
assert(p.length == 20);
Mallocator.instance.deallocate(p);
p = Mallocator.instance.allocate(0);
assert(p.length == 0);
}
/**
* 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) @nogc nothrow pure shared @system
{
if (p !is null)
{
(cast(FreeType) &free)(p.ptr - psize);
}
return true;
}
///
@nogc nothrow pure @system unittest
{
void[] p;
assert(Mallocator.instance.deallocate(p));
p = Mallocator.instance.allocate(10);
assert(Mallocator.instance.deallocate(p));
}
/**
* Reallocating in place isn't supported.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: $(D_KEYWORD false).
*/
bool reallocateInPlace(ref void[] p, size_t size)
@nogc nothrow pure shared @system
{
cast(void) size;
return false;
}
///
@nogc nothrow pure @system unittest
{
void[] p;
assert(!Mallocator.instance.reallocateInPlace(p, 8));
}
/**
* Increases or decreases the size of a memory block.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: Whether the reallocation was successful.
*/
bool reallocate(ref void[] p, size_t size)
@nogc nothrow pure shared @system
{
if (size == 0)
{
if (deallocate(p))
{
p = null;
return true;
}
}
else if (p is null)
{
p = allocate(size);
return p is null ? false : true;
}
else
{
auto r = (cast(ReallocType) &realloc)(p.ptr - psize, size + psize);
if (r !is null)
{
p = r[psize .. psize + size];
return true;
}
}
return false;
}
///
@nogc nothrow pure @system unittest
{
void[] p;
assert(Mallocator.instance.reallocate(p, 20));
assert(p.length == 20);
assert(Mallocator.instance.reallocate(p, 30));
assert(p.length == 30);
assert(Mallocator.instance.reallocate(p, 10));
assert(p.length == 10);
assert(Mallocator.instance.reallocate(p, 0));
assert(p is null);
}
// Fails with false
@nogc nothrow pure @system unittest
{
void[] p = Mallocator.instance.allocate(20);
void[] oldP = p;
assert(!Mallocator.instance.reallocate(p, size_t.max - Mallocator.psize * 2));
assert(oldP is p);
Mallocator.instance.deallocate(p);
}
/**
* Returns: The alignment offered.
*/
@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() @nogc nothrow @system
{
if (instance_ is null)
{
const size = __traits(classInstanceSize, Mallocator) + psize;
void* p = malloc(size);
if (p !is null)
{
p[psize .. size] = typeid(Mallocator).initializer[];
instance_ = cast(shared Mallocator) p[psize .. size].ptr;
}
}
return instance_;
}
/**
* Static allocator instance and initializer.
*
* Returns: The global $(D_PSYMBOL Allocator) instance.
*/
static @property shared(Mallocator) instance() @nogc nothrow pure @system
{
return (cast(GetPureInstance!Mallocator) &instantiate)();
}
///
@nogc nothrow pure @system unittest
{
assert(instance is instance);
}
private enum ushort psize = 8;
private shared static Mallocator instance_;
}

File diff suppressed because it is too large Load Diff

437
source/tanya/memory/op.d Normal file
View File

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

View File

@ -3,140 +3,346 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Copyright: Eugene Wissner 2016. * Dynamic memory management.
*
* Copyright: Eugene Wissner 2016-2018.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * 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)
*/ */
module tanya.memory; module tanya.memory;
import core.exception; import tanya.algorithm.mutation;
public import std.experimental.allocator : make, makeArray; import tanya.conv;
import std.traits; import tanya.exception;
public import tanya.memory.allocator; public import tanya.memory.allocator;
public import tanya.memory.types; import tanya.meta.trait;
import tanya.range.primitive;
/**
* The mixin generates common methods for classes and structs using
* allocators. It provides a protected member, constructor and a read-only property,
* that checks if an allocator was already set and sets it to the default
* one, if not (useful for structs which don't have a default constructor).
*/
mixin template DefaultAllocator()
{
/// Allocator.
protected shared Allocator allocator_;
/**
* Params:
* allocator = The allocator should be used.
*
* Precondition: $(D_INLINECODE allocator_ !is null)
*/
this(shared Allocator allocator) @nogc nothrow pure @safe
in
{
assert(allocator !is null);
}
do
{
this.allocator_ = allocator;
}
/**
* This property checks if the allocator was set in the constructor
* and sets it to the default one, if not.
*
* Returns: Used allocator.
*
* Postcondition: $(D_INLINECODE allocator !is null)
*/
@property shared(Allocator) allocator() @nogc nothrow pure @safe
out (allocator)
{
assert(allocator !is null);
}
do
{
if (allocator_ is null)
{
allocator_ = defaultAllocator;
}
return allocator_;
}
/// ditto
@property shared(Allocator) allocator() const @nogc nothrow pure @trusted
out (allocator)
{
assert(allocator !is null);
}
do
{
if (allocator_ is null)
{
return defaultAllocator;
}
return cast(shared Allocator) allocator_;
}
}
// From druntime // From druntime
private extern (C) void _d_monitordelete(Object h, bool det) nothrow @nogc; extern (C)
private void _d_monitordelete(Object h, bool det) @nogc nothrow pure;
shared Allocator allocator; shared Allocator allocator;
shared static this() nothrow @trusted @nogc private shared(Allocator) getAllocatorInstance() @nogc nothrow
{ {
import tanya.memory.mmappool; if (allocator is null)
allocator = MmapPool.instance; {
version (TanyaNative)
{
import tanya.memory.mmappool;
defaultAllocator = MmapPool.instance;
}
else
{
import tanya.memory.mallocator;
defaultAllocator = Mallocator.instance;
}
}
return allocator;
} }
@property ref shared(Allocator) defaultAllocator() nothrow @safe @nogc /**
* Returns: Default allocator.
*
* Postcondition: $(D_INLINECODE allocator !is null).
*/
@property shared(Allocator) defaultAllocator() @nogc nothrow pure @trusted
out (allocator)
{ {
return allocator; assert(allocator !is null);
}
do
{
return (cast(GetPureInstance!Allocator) &getAllocatorInstance)();
} }
@property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc /**
* Sets the default allocator.
*
* Params:
* allocator = $(D_PSYMBOL Allocator) instance.
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
@property void defaultAllocator(shared(Allocator) allocator) @nogc nothrow @safe
in
{ {
.allocator = allocator; assert(allocator !is null);
}
do
{
.allocator = allocator;
} }
/** /**
* Returns the size in bytes of the state that needs to be allocated to hold an * Returns the size in bytes of the state that needs to be allocated to hold an
* object of type $(D_PARAM T). * 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: * Params:
* T = Object type. * T = Object type.
*
* Returns: Size of an instance of type $(D_PARAM T).
*/ */
template stateSize(T) template stateSize(T)
{ {
static if (is(T == class) || is(T == interface)) static if (isPolymorphicType!T)
{ {
enum stateSize = __traits(classInstanceSize, T); enum size_t stateSize = __traits(classInstanceSize, T);
} }
else else
{ {
enum stateSize = T.sizeof; 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: * Params:
* size = Raw size. * size = Raw size.
* alignment = Alignment. * alignment = Alignment.
* *
* Returns: Aligned size. * Returns: Aligned size.
*/ */
size_t alignedSize(in size_t size, in size_t alignment = 8) pure nothrow @safe @nogc size_t alignedSize(const size_t size, const size_t alignment = 8)
pure nothrow @safe @nogc
{ {
return (size - 1) / alignment * alignment + alignment; return (size - 1) / alignment * alignment + alignment;
} }
/** /*
* Internal function used to create, resize or destroy a dynamic array. It * Internal function used to create, resize or destroy a dynamic array. It
* throws $(D_PSYMBOL OutOfMemoryError) if $(D_PARAM Throws) is set. The new * may throw $(D_PSYMBOL OutOfMemoryError). The new
* allocated part of the array is initialized only if $(D_PARAM Init) * allocated part of the array isn't initialized. This function can be trusted
* is set. This function can be trusted only in the data structures that * only in the data structures that can ensure that the array is
* can ensure that the array is allocated/rellocated/deallocated with the * allocated/rellocated/deallocated with the same allocator.
* same allocator.
* *
* Params: * Params:
* T = Element type of the array being created. * T = Element type of the array being created.
* Init = If should be initialized. * allocator = The allocator used for getting memory.
* Throws = If $(D_PSYMBOL OutOfMemoryError) should be throwsn. * array = A reference to the array being changed.
* allocator = The allocator used for getting memory. * length = New array length.
* array = A reference to the array being changed.
* length = New array length.
* *
* Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could * Returns: $(D_PARAM array).
* not be reallocated. In the latter
*/ */
package(tanya) bool resize(T, package(tanya) T[] resize(T)(shared Allocator allocator,
bool Init = true, auto ref T[] array,
bool Throws = true) const size_t length) @trusted
(shared Allocator allocator,
ref T[] array,
in size_t length) @trusted
{ {
void[] buf = array; if (length == 0)
static if (Init) {
{ if (allocator.deallocate(array))
immutable oldLength = array.length; {
} return null;
if (!allocator.reallocate(buf, length * T.sizeof)) }
{ else
static if (Throws) {
{ onOutOfMemoryError();
onOutOfMemoryError; }
} }
return false;
}
// Casting from void[] is unsafe, but we know we cast to the original type.
array = cast(T[]) buf;
static if (Init) void[] buf = array;
{ if (!allocator.reallocate(buf, length * T.sizeof))
if (oldLength < length) {
{ onOutOfMemoryError();
array[oldLength .. $] = T.init; }
} // Casting from void[] is unsafe, but we know we cast to the original type.
} array = cast(T[]) buf;
return true;
return array;
} }
package(tanya) alias resizeArray = resize;
/// @nogc nothrow pure @safe unittest
unittest
{ {
int[] p; int[] p;
defaultAllocator.resizeArray(p, 20); p = defaultAllocator.resize(p, 20);
assert(p.length == 20); assert(p.length == 20);
defaultAllocator.resizeArray(p, 30); p = defaultAllocator.resize(p, 30);
assert(p.length == 30); assert(p.length == 30);
defaultAllocator.resizeArray(p, 10); p = defaultAllocator.resize(p, 10);
assert(p.length == 10); assert(p.length == 10);
defaultAllocator.resizeArray(p, 0); p = defaultAllocator.resize(p, 0);
assert(p is null); assert(p is null);
}
/*
* Destroys the object.
* Returns the memory should be freed.
*/
package(tanya) void[] finalize(T)(ref T* p)
{
if (p is null)
{
return null;
}
static if (hasElaborateDestructor!T)
{
destroy(*p);
}
return (cast(void*) p)[0 .. T.sizeof];
}
package(tanya) void[] finalize(T)(ref T p)
if (isPolymorphicType!T)
{
if (p is null)
{
return null;
}
static if (is(T == interface))
{
version(Windows)
{
import core.sys.windows.unknwn : IUnknown;
static assert(!is(T : IUnknown), "COM interfaces can't be destroyed in "
~ __PRETTY_FUNCTION__);
}
auto ob = cast(Object) p;
}
else
{
alias ob = p;
}
auto ptr = cast(void*) ob;
auto support = ptr[0 .. typeid(ob).initializer.length];
auto ppv = cast(void**) ptr;
if (!*ppv)
{
return null;
}
auto pc = cast(ClassInfo*) *ppv;
scope (exit)
{
*ppv = null;
}
auto c = *pc;
do
{
// Assume the destructor is @nogc. Leave it nothrow since the destructor
// shouldn't throw and if it does, it is an error anyway.
if (c.destructor)
{
alias DtorType = void function(Object) pure nothrow @safe @nogc;
(cast(DtorType) c.destructor)(ob);
}
}
while ((c = c.base) !is null);
if (ppv[1]) // if monitor is not null
{
_d_monitordelete(cast(Object) ptr, true);
}
return support;
}
package(tanya) void[] finalize(T)(ref T[] p)
{
destroyAll(p);
return p;
} }
/** /**
@ -145,90 +351,158 @@ unittest
* allocator. * allocator.
* *
* Params: * Params:
* T = Type of $(D_PARAM p). * T = Type of $(D_PARAM p).
* allocator = Allocator the $(D_PARAM p) was allocated with. * allocator = Allocator the $(D_PARAM p) was allocated with.
* p = Object or array to be destroyed. * p = Object or array to be destroyed.
*/ */
void dispose(T)(shared Allocator allocator, auto ref T* p)
{
static if (hasElaborateDestructor!T)
{
destroy(*p);
}
() @trusted { allocator.deallocate((cast(void*) p)[0 .. T.sizeof]); }();
p = null;
}
/// Ditto.
void dispose(T)(shared Allocator allocator, auto ref T p) void dispose(T)(shared Allocator allocator, auto ref T p)
if (is(T == class) || is(T == interface))
{ {
if (p is null) () @trusted { allocator.deallocate(finalize(p)); }();
{ p = null;
return;
}
static if (is(T == interface))
{
version(Windows)
{
import core.sys.windows.unknwn : IUnknown;
static assert(!is(T: IUnknown), "COM interfaces can't be destroyed in "
~ __PRETTY_FUNCTION__);
}
auto ob = cast(Object) p;
}
else
{
alias ob = p;
}
auto ptr = cast(void *) ob;
auto support = ptr[0 .. typeid(ob).initializer.length];
scope (success)
{
() @trusted { allocator.deallocate(support); }();
p = null;
}
auto ppv = cast(void**) ptr;
if (!*ppv)
{
return;
}
auto pc = cast(ClassInfo*) *ppv;
scope (exit)
{
*ppv = null;
}
auto c = *pc;
do
{
// Assume the destructor is @nogc. Leave it nothrow since the destructor
// shouldn't throw and if it does, it is an error anyway.
if (c.destructor)
{
(cast(void function (Object) nothrow @safe @nogc) c.destructor)(ob);
}
}
while ((c = c.base) !is null);
if (ppv[1]) // if monitor is not null
{
_d_monitordelete(cast(Object) ptr, true);
}
} }
/// Ditto. @nogc nothrow pure @system unittest
void dispose(T)(shared Allocator allocator, auto ref T[] array)
{ {
static if (hasElaborateDestructor!(typeof(array[0]))) static struct S
{ {
foreach (ref e; array) ~this() @nogc nothrow pure @safe
{ {
destroy(e); }
} }
} auto p = cast(S[]) defaultAllocator.allocate(S.sizeof);
() @trusted { allocator.deallocate(array); }();
array = null; defaultAllocator.dispose(p);
}
// Works with interfaces.
@nogc nothrow pure @safe unittest
{
interface I
{
}
class C : I
{
}
auto c = defaultAllocator.make!C();
I i = c;
defaultAllocator.dispose(i);
defaultAllocator.dispose(i);
}
/**
* Constructs a new class instance of type $(D_PARAM T) using $(D_PARAM args)
* as the parameter list for the constructor of $(D_PARAM T).
*
* Params:
* T = Class type.
* A = Types of the arguments to the constructor of $(D_PARAM T).
* allocator = Allocator.
* args = Constructor arguments of $(D_PARAM T).
*
* Returns: Newly created $(D_PSYMBOL T).
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
T make(T, A...)(shared Allocator allocator, auto ref A args)
if (is(T == class))
in
{
assert(allocator !is null);
}
do
{
auto mem = (() @trusted => allocator.allocate(stateSize!T))();
if (mem is null)
{
onOutOfMemoryError();
}
scope (failure)
{
() @trusted { allocator.deallocate(mem); }();
}
return emplace!T(mem[0 .. stateSize!T], args);
}
/**
* Constructs a value object of type $(D_PARAM T) using $(D_PARAM args)
* as the parameter list for the constructor of $(D_PARAM T) and returns a
* pointer to the new object.
*
* Params:
* T = Object type.
* A = Types of the arguments to the constructor of $(D_PARAM T).
* allocator = Allocator.
* args = Constructor arguments of $(D_PARAM T).
*
* Returns: Pointer to the created object.
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
T* make(T, A...)(shared Allocator allocator, auto ref A args)
if (!is(T == interface)
&& !is(T == class)
&& !isAssociativeArray!T
&& !isArray!T)
in
{
assert(allocator !is null);
}
do
{
auto mem = (() @trusted => allocator.allocate(stateSize!T))();
if (mem is null)
{
onOutOfMemoryError();
}
scope (failure)
{
() @trusted { allocator.deallocate(mem); }();
}
return emplace!T(mem[0 .. stateSize!T], args);
}
///
@nogc nothrow pure @safe unittest
{
int* i = defaultAllocator.make!int(5);
assert(*i == 5);
defaultAllocator.dispose(i);
}
/**
* Constructs a new array with $(D_PARAM n) elements.
*
* Params:
* T = Array type.
* 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);
}
do
{
auto ret = allocator.resize!(ElementType!T)(null, n);
ret.uninitializedFill(ElementType!T.init);
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);
} }

View File

@ -0,0 +1,916 @@
/* 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/. */
/**
* Smart pointers.
*
* A smart pointer is an object that wraps a raw pointer or a reference
* (class, dynamic array) to manage its lifetime.
*
* This module provides two kinds of lifetime management strategies:
* $(UL
* $(LI Reference counting)
* $(LI Unique ownership)
* )
*
* Copyright: Eugene Wissner 2016-2018.
* 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,
* tanya/memory/smartref.d)
*/
module tanya.memory.smartref;
import tanya.algorithm.comparison;
import tanya.algorithm.mutation;
import tanya.conv;
import tanya.exception;
import tanya.memory;
import tanya.meta.trait;
import tanya.range.primitive;
version (unittest) import tanya.test.stub;
private template Payload(T)
{
static if (isPolymorphicType!T || isArray!T)
{
alias Payload = T;
}
else
{
alias Payload = T*;
}
}
private final class RefCountedStore(T)
{
T payload;
size_t counter = 1;
size_t opUnary(string op)()
if (op == "--" || op == "++")
in
{
assert(this.counter > 0);
}
do
{
mixin("return " ~ op ~ "counter;");
}
int opCmp(const size_t counter)
{
if (this.counter > counter)
{
return 1;
}
else if (this.counter < counter)
{
return -1;
}
else
{
return 0;
}
}
}
private void separateDeleter(T)(RefCountedStore!T storage,
shared Allocator allocator)
{
allocator.dispose(storage.payload);
allocator.dispose(storage);
}
private void unifiedDeleter(T)(RefCountedStore!T storage,
shared Allocator allocator)
{
auto ptr1 = finalize(storage);
auto ptr2 = finalize(storage.payload);
allocator.deallocate(ptr1.ptr[0 .. ptr1.length + ptr2.length]);
}
/**
* Reference-counted object containing a $(D_PARAM T) value as payload.
* $(D_PSYMBOL RefCounted) keeps track of all references of an object, and
* when the reference count goes down to zero, frees the underlying store.
*
* Params:
* T = Type of the reference-counted value.
*/
struct RefCounted(T)
{
private alias Storage = RefCountedStore!(Payload!T);
private Storage storage;
private void function(Storage storage,
shared Allocator allocator) @nogc deleter;
invariant
{
assert(this.storage is null || this.allocator_ !is null);
assert(this.storage is null || this.deleter !is null);
}
/**
* Takes ownership over $(D_PARAM value), setting the counter to 1.
* $(D_PARAM value) may be a pointer, an object or a dynamic array.
*
* Params:
* value = Value whose ownership is taken over.
* allocator = Allocator used to destroy the $(D_PARAM value) and to
* allocate/deallocate internal storage.
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
this(Payload!T value, shared Allocator allocator = defaultAllocator)
{
this(allocator);
this.storage = allocator.make!Storage();
this.deleter = &separateDeleter!(Payload!T);
this.storage.payload = value;
}
/// ditto
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
do
{
this.allocator_ = allocator;
}
/**
* Increases the reference counter by one.
*/
this(this)
{
if (count != 0)
{
++this.storage;
}
}
/**
* Decreases the reference counter by one.
*
* If the counter reaches 0, destroys the owned object.
*/
~this()
{
if (this.storage !is null && !(this.storage > 0 && --this.storage))
{
deleter(this.storage, allocator);
}
}
/**
* Takes ownership over $(D_PARAM rhs). Initializes this
* $(D_PSYMBOL RefCounted) if needed.
*
* If it is the last reference of the previously owned object,
* it will be destroyed.
*
* To reset $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
*
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
* be used. If you need a different allocator, create a new
* $(D_PSYMBOL RefCounted) and assign it.
*
* Params:
* rhs = New object.
*
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) opAssign(Payload!T rhs)
{
if (this.storage is null)
{
this.storage = allocator.make!Storage();
this.deleter = &separateDeleter!(Payload!T);
}
else if (this.storage > 1)
{
--this.storage;
this.storage = allocator.make!Storage();
this.deleter = &separateDeleter!(Payload!T);
}
else
{
finalize(this.storage.payload);
this.storage.payload = Payload!T.init;
}
this.storage.payload = rhs;
return this;
}
/// ditto
ref typeof(this) opAssign(typeof(null))
{
if (this.storage is null)
{
return this;
}
else if (this.storage > 1)
{
--this.storage;
}
else
{
deleter(this.storage, allocator);
}
this.storage = null;
return this;
}
/// ditto
ref typeof(this) opAssign(typeof(this) rhs)
{
swap(this.allocator_, rhs.allocator_);
swap(this.storage, rhs.storage);
swap(this.deleter, rhs.deleter);
return this;
}
/**
* Returns: Reference to the owned object.
*
* Precondition: $(D_INLINECODE cound > 0).
*/
inout(Payload!T) get() inout
in
{
assert(count > 0, "Attempted to access an uninitialized reference");
}
do
{
return this.storage.payload;
}
version (D_Ddoc)
{
/**
* Dereferences the pointer. It is defined only for pointers, not for
* reference types like classes, that can be accessed directly.
*
* Params:
* op = Operation.
*
* Returns: Reference to the pointed value.
*/
ref inout(T) opUnary(string op)() inout
if (op == "*");
}
else static if (isPointer!(Payload!T))
{
ref inout(T) opUnary(string op)() inout
if (op == "*")
{
return *this.storage.payload;
}
}
/**
* Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal
* storage.
*/
@property bool isInitialized() const
{
return this.storage !is null;
}
/**
* Returns: The number of $(D_PSYMBOL RefCounted) instances that share
* ownership over the same pointer (including $(D_KEYWORD this)).
* If this $(D_PSYMBOL RefCounted) isn't initialized, returns `0`.
*/
@property size_t count() const
{
return this.storage is null ? 0 : this.storage.counter;
}
mixin DefaultAllocator;
alias get this;
}
///
@nogc @system unittest
{
auto rc = RefCounted!int(defaultAllocator.make!int(5), defaultAllocator);
auto val = rc.get();
*val = 8;
assert(*rc.storage.payload == 8);
val = null;
assert(rc.storage.payload !is null);
assert(*rc.storage.payload == 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));
}
/**
* Constructs a new object of type $(D_PARAM T) and wraps it in a
* $(D_PSYMBOL RefCounted) using $(D_PARAM args) as the parameter list for
* the constructor of $(D_PARAM T).
*
* This function is more efficient than the using of $(D_PSYMBOL RefCounted)
* directly, since it allocates only ones (the internal storage and the
* object).
*
* Params:
* T = Type of the constructed object.
* 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)
*/
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);
}
do
{
auto rc = typeof(return)(allocator);
const storageSize = alignedSize(stateSize!(RefCounted!T.Storage));
const size = alignedSize(stateSize!T + storageSize);
auto mem = (() @trusted => allocator.allocate(size))();
if (mem is null)
{
onOutOfMemoryError();
}
scope (failure)
{
() @trusted { allocator.deallocate(mem); }();
}
rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]);
rc.storage.payload = emplace!T(mem[storageSize .. $], args);
rc.deleter = &unifiedDeleter!(Payload!T);
return rc;
}
/**
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
* $(D_PSYMBOL RefCounted).
*
* Params:
* T = Array 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)
*/
RefCounted!T refCounted(T)(shared Allocator allocator, const size_t size)
@trusted
if (isArray!T)
in
{
assert(allocator !is null);
assert(size <= size_t.max / ElementType!T.sizeof);
}
do
{
return RefCounted!T(allocator.make!T(size), allocator);
}
///
@nogc @system unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(rc.count == 1);
void func(RefCounted!int param) @nogc
{
if (param.count == 2)
{
func(param);
}
else
{
assert(param.count == 3);
}
}
func(rc);
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
{
size_t destroyed;
{
auto rc = defaultAllocator.refCounted!WithDtor(destroyed);
}
assert(destroyed == 1);
}
/**
* $(D_PSYMBOL Unique) stores an object that gets destroyed at the end of its scope.
*
* Params:
* T = Value type.
*/
struct Unique(T)
{
private Payload!T payload;
invariant
{
assert(payload is null || allocator_ !is null);
}
/**
* Takes ownership over $(D_PARAM value), setting the counter to 1.
* $(D_PARAM value) may be a pointer, an object or a dynamic array.
*
* Params:
* value = Value whose ownership is taken over.
* allocator = Allocator used to destroy the $(D_PARAM value) and to
* allocate/deallocate internal storage.
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
this(Payload!T value, shared Allocator allocator = defaultAllocator)
{
this(allocator);
this.payload = value;
}
/// ditto
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
do
{
this.allocator_ = allocator;
}
/**
* $(D_PSYMBOL Unique) is noncopyable.
*/
@disable this(this);
/**
* Destroys the owned object.
*/
~this()
{
allocator.dispose(this.payload);
}
/**
* Initialized this $(D_PARAM Unique) and takes ownership over
* $(D_PARAM rhs).
*
* To reset $(D_PSYMBOL Unique) assign $(D_KEYWORD null).
*
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
* be used. If you need a different allocator, create a new
* $(D_PSYMBOL Unique) and assign it.
*
* Params:
* rhs = New object.
*
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) opAssign(Payload!T rhs)
{
allocator.dispose(this.payload);
this.payload = rhs;
return this;
}
/// ditto
ref typeof(this) opAssign(typeof(null))
{
allocator.dispose(this.payload);
return this;
}
/// ditto
ref typeof(this) opAssign(typeof(this) rhs)
{
swap(this.allocator_, rhs.allocator_);
swap(this.payload, rhs.payload);
return this;
}
///
@nogc nothrow pure @system unittest
{
auto rc = defaultAllocator.unique!int(5);
rc = defaultAllocator.make!int(7);
assert(*rc == 7);
}
/**
* Returns: Reference to the owned object.
*/
inout(Payload!T) get() inout
{
return this.payload;
}
version (D_Ddoc)
{
/**
* Dereferences the pointer. It is defined only for pointers, not for
* reference types like classes, that can be accessed directly.
*
* Params:
* op = Operation.
*
* Returns: Reference to the pointed value.
*/
ref inout(T) opUnary(string op)() inout
if (op == "*");
}
else static if (isPointer!(Payload!T))
{
ref inout(T) opUnary(string op)() inout
if (op == "*")
{
return *this.payload;
}
}
/**
* Returns: Whether this $(D_PSYMBOL Unique) holds some value.
*/
@property bool isInitialized() const
{
return this.payload !is null;
}
///
@nogc nothrow pure @system unittest
{
Unique!int u;
assert(!u.isInitialized);
}
/**
* Sets the internal pointer to $(D_KEYWORD). The allocator isn't changed.
*
* Returns: Reference to the owned object.
*/
Payload!T release()
{
auto payload = this.payload;
this.payload = null;
return payload;
}
///
@nogc nothrow pure @system unittest
{
auto u = defaultAllocator.unique!int(5);
assert(u.isInitialized);
auto i = u.release();
assert(*i == 5);
assert(!u.isInitialized);
}
mixin DefaultAllocator;
alias get this;
}
///
@nogc nothrow pure @system unittest
{
auto p = defaultAllocator.make!int(5);
auto s = Unique!int(p, defaultAllocator);
assert(*s == 5);
}
///
@nogc nothrow @system unittest
{
static bool destroyed;
static struct F
{
~this() @nogc nothrow @safe
{
destroyed = true;
}
}
{
auto s = Unique!F(defaultAllocator.make!F(), defaultAllocator);
}
assert(destroyed);
}
/**
* Constructs a new object of type $(D_PARAM T) and wraps it in a
* $(D_PSYMBOL Unique) using $(D_PARAM args) as the parameter list for
* the constructor of $(D_PARAM T).
*
* Params:
* T = Type of the constructed object.
* 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)
*/
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);
}
do
{
auto payload = allocator.make!(T, A)(args);
return Unique!T(payload, allocator);
}
/**
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
* $(D_PSYMBOL Unique).
*
* Params:
* T = Array 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)
*/
Unique!T unique(T)(shared Allocator allocator, const size_t size)
@trusted
if (isArray!T)
in
{
assert(allocator !is null);
assert(size <= size_t.max / ElementType!T.sizeof);
}
do
{
auto payload = allocator.resize!(ElementType!T)(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);
}

View File

@ -1,458 +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 2016.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
*/
module tanya.memory.types;
import core.exception;
import std.algorithm.comparison;
import std.algorithm.mutation;
import std.conv;
import std.traits;
import tanya.memory;
/**
* Reference-counted object containing a $(D_PARAM T) value as payload.
* $(D_PSYMBOL RefCounted) keeps track of all references of an object, and
* when the reference count goes down to zero, frees the underlying store.
*
* Params:
* T = Type of the reference-counted value.
*/
struct RefCounted(T)
{
static if (is(T == class) || is(T == interface))
{
private alias Payload = T;
}
else
{
private alias Payload = T*;
}
private class Storage
{
private Payload payload;
private size_t counter = 1;
private final size_t opUnary(string op)() pure nothrow @safe @nogc
if (op == "--" || op == "++")
in
{
assert(counter > 0);
}
body
{
mixin("return " ~ op ~ "counter;");
}
private final int opCmp(size_t counter) const pure nothrow @safe @nogc
{
if (this.counter > counter)
{
return 1;
}
else if (this.counter < counter)
{
return -1;
}
else
{
return 0;
}
}
private final int opEquals(size_t counter) const pure nothrow @safe @nogc
{
return this.counter == counter;
}
}
private final class RefCountedStorage : Storage
{
private shared Allocator allocator;
this(shared Allocator allocator) pure nothrow @safe @nogc
in
{
assert(allocator !is null);
}
body
{
this.allocator = allocator;
}
~this() nothrow @nogc
{
allocator.dispose(payload);
}
}
private Storage storage;
invariant
{
assert(storage is null || allocator_ !is null);
}
/**
* Takes ownership over $(D_PARAM value), setting the counter to 1.
* $(D_PARAM value) may be a pointer, an object or a dynamic array.
*
* Params:
* value = Value whose ownership is taken over.
* allocator = Allocator used to destroy the $(D_PARAM value) and to
* allocate/deallocate internal storage.
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
this(Payload value, shared Allocator allocator = defaultAllocator)
{
this(allocator);
storage = allocator.make!RefCountedStorage(allocator);
move(value, storage.payload);
}
/// Ditto.
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
{
this.allocator_ = allocator;
}
/**
* Increases the reference counter by one.
*/
this(this)
{
if (count != 0)
{
++storage;
}
}
/**
* Decreases the reference counter by one.
*
* If the counter reaches 0, destroys the owned object.
*/
~this()
{
if (storage !is null && !(storage.counter && --storage))
{
allocator_.dispose(storage);
}
}
/**
* Takes ownership over $(D_PARAM rhs). Initializes this
* $(D_PSYMBOL RefCounted) if needed.
*
* If it is the last reference of the previously owned object,
* it will be destroyed.
*
* To reset the $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
*
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
* be used. If you need a different allocator, create a new
* $(D_PSYMBOL RefCounted) and assign it.
*
* Params:
* rhs = $(D_KEYWORD this).
*/
ref typeof(this) opAssign(Payload rhs)
{
if (storage is null)
{
storage = allocator.make!RefCountedStorage(allocator);
}
else if (storage > 1)
{
--storage;
storage = allocator.make!RefCountedStorage(allocator);
}
else if (cast(RefCountedStorage) storage is null)
{
// Created with refCounted. Always destroyed togethter with the pointer.
assert(storage.counter != 0);
allocator.dispose(storage);
storage = allocator.make!RefCountedStorage(allocator);
}
else
{
allocator.dispose(storage.payload);
}
move(rhs, storage.payload);
return this;
}
/// Ditto.
ref typeof(this) opAssign(typeof(null))
{
if (storage is null)
{
return this;
}
else if (storage > 1)
{
--storage;
storage = null;
}
else if (cast(RefCountedStorage) storage is null)
{
// Created with refCounted. Always destroyed togethter with the pointer.
assert(storage.counter != 0);
allocator.dispose(storage);
return this;
}
else
{
allocator.dispose(storage.payload);
}
return this;
}
/// Ditto.
ref typeof(this) opAssign(typeof(this) rhs)
{
swap(allocator_, rhs.allocator_);
swap(storage, rhs.storage);
return this;
}
/**
* Returns: Reference to the owned object.
*/
inout(Payload) get() inout pure nothrow @safe @nogc
in
{
assert(count > 0, "Attempted to access an uninitialized reference.");
}
body
{
return storage.payload;
}
static if (isPointer!Payload)
{
/**
* Params:
* op = Operation.
*
* Dereferences the pointer. It is defined only for pointers, not for
* reference types like classes, that can be accessed directly.
*
* Returns: Reference to the pointed value.
*/
ref T opUnary(string op)()
if (op == "*")
{
return *storage.payload;
}
}
/**
* Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal
* storage.
*/
@property bool isInitialized() const
{
return storage !is null;
}
/**
* Returns: The number of $(D_PSYMBOL RefCounted) instances that share
* ownership over the same pointer (including $(D_KEYWORD this)).
* If this $(D_PSYMBOL RefCounted) isn't initialized, returns `0`.
*/
@property size_t count() const
{
return storage is null ? 0 : storage.counter;
}
mixin DefaultAllocator;
alias get this;
}
///
unittest
{
auto rc = RefCounted!int(defaultAllocator.make!int(5), defaultAllocator);
auto val = rc.get;
*val = 8;
assert(*rc.storage.payload == 8);
val = null;
assert(rc.storage.payload !is null);
assert(*rc.storage.payload == 8);
*rc = 9;
assert(*rc.storage.payload == 9);
}
version (unittest)
{
private class A
{
uint *destroyed;
this(ref uint destroyed) @nogc
{
this.destroyed = &destroyed;
}
~this() @nogc
{
++(*destroyed);
}
}
private struct B
{
int prop;
@disable this();
this(int param1) @nogc
{
prop = param1;
}
}
}
private 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)
{
assert(rc.count == 2);
}
func(rc);
assert(rc.count == 1);
}
assert(destroyed == 1);
RefCounted!int rc;
assert(rc.count == 0);
rc = defaultAllocator.make!int(8);
assert(rc.count == 1);
}
private 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));
}
/**
* Constructs a new object of type $(D_PARAM T) and wraps it in a
* $(D_PSYMBOL RefCounted) using $(D_PARAM args) as the parameter list for
* the constructor of $(D_PARAM T).
*
* This function is more efficient than the using of $(D_PSYMBOL RefCounted)
* directly, since it allocates only ones (the internal storage and the
* object).
*
* Params:
* T = Type of the constructed object.
* 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).
*/
RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
if (!is(T == interface) && !isAbstractClass!T
&& !isArray!T && !isAssociativeArray!T)
{
auto rc = typeof(return)(allocator);
immutable storageSize = alignedSize(stateSize!(RefCounted!T.Storage));
immutable size = alignedSize(stateSize!T + storageSize);
auto mem = (() @trusted => allocator.allocate(size))();
if (mem is null)
{
onOutOfMemoryError();
}
scope (failure)
{
() @trusted { allocator.deallocate(mem); }();
}
rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]);
static if (is(T == class))
{
rc.storage.payload = emplace!T(mem[storageSize .. $], args);
}
else
{
auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
rc.storage.payload = emplace!T(ptr, args);
}
return rc;
}
///
unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(rc.count == 1);
void func(RefCounted!int param)
{
if (param.count == 2)
{
func(param);
}
else
{
assert(param.count == 3);
}
}
func(rc);
assert(rc.count == 1);
}
private @nogc unittest
{
struct E
{
}
auto b = defaultAllocator.refCounted!B(15);
static assert(is(typeof(b.storage.payload) == B*));
static assert(is(typeof(b.prop) == int));
static assert(!is(typeof(defaultAllocator.refCounted!B())));
static assert(is(typeof(defaultAllocator.refCounted!E())));
static assert(!is(typeof(defaultAllocator.refCounted!E(5))));
{
auto rc = defaultAllocator.refCounted!B(3);
assert(rc.get.prop == 3);
}
{
auto rc = defaultAllocator.refCounted!E();
assert(rc.count);
}
}

View File

@ -1,49 +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/. */
/**
* Templates that generate values.
*
* Copyright: Eugene Wissner 2016.
* 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.meta.gen;
import std.traits;
/**
* Initializer list.
*
* Generates a static array with elements from $(D_PARAM args). All elements
* should have the same type. It can be used in constructors which accept a
* list of the elements of the same type in the situations where variadic
* functions and templates can't be used.
*
* Params:
* Args = Argument type.
* args = Arguments.
*/
enum IL(Args...)(Args args)
if (Args.length > 0)
{
alias BaseType = typeof(args[0]);
BaseType[args.length] result;
foreach (i, a; args)
{
static assert(isImplicitlyConvertible!(typeof(a), BaseType));
result[i] = a;
}
return result;
}
///
unittest
{
static assert(IL(1, 5, 8).length == 3);
static assert(IL(1, 5, 8).sizeof == 3 * int.sizeof);
}

File diff suppressed because it is too large Load Diff

View File

@ -3,13 +3,21 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Metaprogramming. * Template metaprogramming.
* *
* Copyright: Eugene Wissner 2016. * This package contains utilities to acquire type information at compile-time,
* to transform from one type to another. It has also different algorithms for
* iterating, searching and modifying template arguments.
*
* Copyright: Eugene Wissner 2017-2018.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/meta/package.d,
* tanya/meta/package.d)
*/ */
module tanya.meta; module tanya.meta;
public import tanya.meta.gen; public import tanya.meta.metafunction;
public import tanya.meta.trait;
public import tanya.meta.transform;

3106
source/tanya/meta/trait.d Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,981 @@
/* 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/. */
/**
* Type transformations.
*
* Templates in this module can be used to modify type qualifiers or transform
* types. They take some type as argument and return a different type after
* perfoming the specified transformation.
*
* Copyright: Eugene Wissner 2017-2018.
* 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,
* tanya/meta/transform.d)
*/
module tanya.meta.transform;
import tanya.meta.metafunction;
import tanya.meta.trait;
/**
* Removes any type qualifiers from $(D_PARAM T).
*
* Removed qualifiers are:
* $(UL
* $(LI const)
* $(LI immutable)
* $(LI inout)
* $(LI shared)
* )
* and combinations of these.
*
* If the type $(D_PARAM T) doesn't have any qualifieres,
* $(D_INLINECODE Unqual!T) becomes an alias for $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_PARAM T) without any type qualifiers.
*/
template Unqual(T)
{
static if (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))
{
alias Unqual = U;
}
else
{
alias Unqual = T;
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(is(Unqual!bool == bool));
static assert(is(Unqual!(immutable bool) == bool));
static assert(is(Unqual!(inout bool) == bool));
static assert(is(Unqual!(inout const bool) == bool));
static assert(is(Unqual!(shared bool) == bool));
static assert(is(Unqual!(shared const bool) == bool));
static assert(is(Unqual!(shared inout const bool) == bool));
}
/**
* If $(D_PARAM T) is an $(D_KEYWORD enum), $(D_INLINECODE OriginalType!T)
* evaluates to the most base type of that $(D_KEYWORD enum) and to
* $(D_PARAM T) otherwise.
*
* Params:
* T = A type.
*
* Returns: Base type of the $(D_KEYWORD enum) $(D_PARAM T) or $(D_PARAM T)
* itself.
*/
template OriginalType(T)
{
static if (is(T U == enum))
{
alias OriginalType = OriginalType!U;
}
else
{
alias OriginalType = T;
}
}
///
@nogc nothrow pure @safe unittest
{
enum E1 : const(int)
{
n = 0,
}
enum E2 : bool
{
t = true,
}
enum E3 : E2
{
t = E2.t,
}
enum E4 : const(E2)
{
t = E2.t,
}
static assert(is(OriginalType!E1 == const int));
static assert(is(OriginalType!E2 == bool));
static assert(is(OriginalType!E3 == bool));
static assert(is(OriginalType!E4 == bool));
static assert(is(OriginalType!(const E4) == bool));
}
/**
* Copies constness of $(D_PARAM From) to $(D_PARAM To).
*
* The following type qualifiers affect the constness and hence are copied:
* $(UL
* $(LI const)
* $(LI immutable)
* $(LI inout)
* $(LI inout const)
* )
*
* Params:
* From = Source type.
* To = Target type.
*
* Returns: $(D_PARAM To) with the constness of $(D_PARAM From).
*
* See_Also: $(D_PSYMBOL CopyTypeQualifiers).
*/
template CopyConstness(From, To)
{
static if (is(From T == immutable T))
{
alias CopyConstness = immutable To;
}
else static if (is(From T == const T) || is(From T == shared const T))
{
alias CopyConstness = const To;
}
else static if (is(From T == inout T) || is(From T == shared inout T))
{
alias CopyConstness = inout To;
}
else static if (is(From T == inout const T)
|| is(From T == shared inout const T))
{
alias CopyConstness = inout const To;
}
else
{
alias CopyConstness = To;
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(is(CopyConstness!(int, char) == char));
static assert(is(CopyConstness!(const int, char) == const char));
static assert(is(CopyConstness!(immutable int, char) == immutable char));
static assert(is(CopyConstness!(inout int, char) == inout char));
static assert(is(CopyConstness!(inout const int, char) == inout const char));
static assert(is(CopyConstness!(shared int, char) == char));
static assert(is(CopyConstness!(shared const int, char) == const char));
static assert(is(CopyConstness!(shared inout int, char) == inout char));
static assert(is(CopyConstness!(shared inout const int, char) == inout const char));
static assert(is(CopyConstness!(const int, shared char) == shared const char));
static assert(is(CopyConstness!(const int, immutable char) == immutable char));
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*`.
*
* Params:
* T = Pointer type.
*
* Returns: Pointer target type.
*/
template PointerTarget(T)
{
static if (is(T U : U*))
{
alias PointerTarget = U;
}
else
{
static assert(T.stringof ~ " isn't a pointer type");
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(is(PointerTarget!(bool*) == bool));
static assert(is(PointerTarget!(const bool*) == const bool));
static assert(is(PointerTarget!(const shared bool*) == const shared bool));
static assert(!is(PointerTarget!bool));
}
/**
* Params:
* T = The type of the associative array.
*
* Returns: The key type of the associative array $(D_PARAM T).
*/
template KeyType(T)
{
static if (is(T V : V[K], K))
{
alias KeyType = K;
}
else
{
static assert(false, T.stringof ~ " isn't an associative array");
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(is(KeyType!(int[string]) == string));
static assert(!is(KeyType!(int[15])));
}
/**
* Params:
* T = The type of the associative array.
*
* Returns: The value type of the associative array $(D_PARAM T).
*/
template ValueType(T)
{
static if (is(T V : V[K], K))
{
alias ValueType = V;
}
else
{
static assert(false, T.stringof ~ " isn't an associative array");
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(is(ValueType!(int[string]) == int));
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).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE inout(T)).
*/
alias InoutOf(T) = inout(T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(InoutOf!int == inout int));
}
/**
* Adds $(D_KEYWORD inout) qualifier to the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE inout(T)).
*/
alias ConstOf(T) = const(T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(ConstOf!int == const int));
}
/**
* Adds $(D_KEYWORD inout) qualifier to the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE inout(T)).
*/
alias SharedOf(T) = shared(T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(SharedOf!int == shared int));
}
/**
* Adds $(D_KEYWORD inout) qualifier to the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE inout(T)).
*/
alias SharedInoutOf(T) = shared(inout T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(SharedInoutOf!int == shared inout int));
}
/**
* Adds $(D_KEYWORD shared const) qualifier to the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE shared(const T)).
*/
alias SharedConstOf(T) = shared(const T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(SharedConstOf!int == shared const int));
}
/**
* Adds $(D_KEYWORD immutable) qualifier to the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE immutable(T)).
*/
alias ImmutableOf(T) = immutable(T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(ImmutableOf!int == immutable int));
}
/**
* Adds $(D_KEYWORD inout const) qualifier to the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE inout(const T)).
*/
alias InoutConstOf(T) = inout(const T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(InoutConstOf!int == inout const int));
}
/**
* Adds $(D_KEYWORD shared inout const) qualifier to the type $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: $(D_INLINECODE shared(inout const T)).
*/
alias SharedInoutConstOf(T) = shared(inout const T);
///
@nogc nothrow pure @safe unittest
{
static assert(is(SharedInoutConstOf!int == shared inout const int));
}
/**
* 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).
*
* $(D_PSYMBOL TypeOf) evaluates to $(D_KEYWORD void) for template arguments.
*
* The symbols that don't have a type and aren't types cannot be used as
* arguments to $(D_PSYMBOL TypeOf).
*
* Params:
* T = Expression, type or template.
*
* Returns: The type of $(D_PARAM T).
*/
alias TypeOf(T) = T;
/// ditto
template TypeOf(alias T)
if (isExpressions!T || __traits(isTemplate, T))
{
alias TypeOf = typeof(T);
}
///
@nogc nothrow pure @safe unittest
{
struct S(T)
{
}
static assert(is(TypeOf!S == void));
static assert(is(TypeOf!int == int));
static assert(is(TypeOf!true == bool));
static assert(!is(TypeOf!(tanya.meta)));
}
// e.g. returns int for int**.
private template FinalPointerTarget(T)
{
static if (isPointer!T)
{
alias FinalPointerTarget = FinalPointerTarget!(PointerTarget!T);
}
else
{
alias FinalPointerTarget = T;
}
}
// Returns true if T1 is void* and T2 is some pointer.
private template voidAndPointer(T1, T2)
{
enum bool voidAndPointer = is(Unqual!(PointerTarget!T1) == void)
&& isPointer!T2;
}
// Type returned by the ternary operator.
private alias TernaryType(T, U) = typeof(true ? T.init : U.init);
/**
* Determines the type all $(D_PARAM Args) can be implicitly converted to.
*
* $(OL
* $(LI If one of the arguments is $(D_KEYWORD void), the common type is
* $(D_KEYWORD void).)
* $(LI The common type of integers with the same sign is the type with a
* larger size. Signed and unsigned integers don't have a common type.
* Type qualifiers are only preserved if all arguments are the same
* type.)
* $(LI The common type of floating point numbers is the type with more
* precision. Type qualifiers are only preserved if all arguments are
* the same type.)
* $(LI The common type of polymorphic objects is the next, more generic type
* both objects inherit from, e.g. $(D_PSYMBOL Object).)
* $(LI `void*` is concerned as a common type of pointers only if one of the
* arguments is a void pointer.)
* $(LI Other types have a common type only if their pointers have a common
* type. It means that for example $(D_KEYWORD bool) and $(D_KEYWORD int)
don't have a common type. If the types fullfill this condition, the
common type is determined with the ternary operator, i.e.
`typeof(true ? T1.init : T2.init)` is evaluated.)
* )
*
* If $(D_PARAM Args) don't have a common type, $(D_PSYMBOL CommonType) is
* $(D_KEYWORD void).
*
* Params:
* Args = Type list.
*
* Returns: Common type for $(D_PARAM Args) or $(D_KEYWORD void) if
* $(D_PARAM Args) don't have a common type.
*/
template CommonType(Args...)
if (allSatisfy!(isType, Args))
{
static if (Args.length == 0
|| is(Unqual!(Args[0]) == void)
|| is(Unqual!(Args[1]) == void))
{
alias CommonType = void;
}
else static if (Args.length == 1)
{
alias CommonType = Args[0];
}
else
{
private alias Pair = Args[0 .. 2];
private enum bool sameSigned = allSatisfy!(isIntegral, Pair)
&& isSigned!(Args[0]) == isSigned!(Args[1]);
static if (is(Args[0] == Args[1]))
{
alias CommonType = CommonType!(Args[0], Args[2 .. $]);
}
else static if (sameSigned || allSatisfy!(isFloatingPoint, Pair))
{
alias CommonType = CommonType!(Unqual!(Largest!Pair),
Args[2 .. $]);
}
else static if (voidAndPointer!Pair
|| voidAndPointer!(Args[1], Args[0]))
{
// Workaround for https://issues.dlang.org/show_bug.cgi?id=15557.
// Determine the qualifiers returned by the ternary operator as if
// both pointers were int*. Then copy the qualifiers to void*.
alias P1 = CopyTypeQualifiers!(FinalPointerTarget!(Args[0]), int)*;
alias P2 = CopyTypeQualifiers!(FinalPointerTarget!(Args[1]), int)*;
static if (is(TernaryType!(P1, P2) U))
{
alias CommonType = CopyTypeQualifiers!(PointerTarget!U, void)*;
}
else
{
alias CommonType = void;
}
}
else static if ((isPointer!(Args[0]) || isPolymorphicType!(Args[0]))
&& is(TernaryType!Pair U))
{
alias CommonType = CommonType!(U, Args[2 .. $]);
}
else static if (is(TernaryType!(Args[0]*, Args[1]*)))
{
alias CommonType = CommonType!(TernaryType!Pair, Args[2 .. $]);
}
else
{
alias CommonType = void;
}
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(is(CommonType!(int, int, int) == int));
static assert(is(CommonType!(ubyte, ushort, uint) == uint));
static assert(is(CommonType!(int, uint) == void));
static assert(is(CommonType!(int, const int) == int));
static assert(is(CommonType!(const int, const int) == const int));
static assert(is(CommonType!(int[], const(int)[]) == const(int)[]));
static assert(is(CommonType!(string, char[]) == const(char)[]));
class A
{
}
static assert(is(CommonType!(const A, Object) == const Object));
}
@nogc nothrow pure @safe unittest
{
static assert(is(CommonType!(void*, int*) == void*));
static assert(is(CommonType!(void*, const(int)*) == const(void)*));
static assert(is(CommonType!(void*, const(void)*) == const(void)*));
static assert(is(CommonType!(int*, void*) == void*));
static assert(is(CommonType!(const(int)*, void*) == const(void)*));
static assert(is(CommonType!(const(void)*, void*) == const(void)*));
static assert(is(CommonType!() == void));
static assert(is(CommonType!(int*, const(int)*) == const(int)*));
static assert(is(CommonType!(int**, const(int)**) == const(int*)*));
static assert(is(CommonType!(float, double) == double));
static assert(is(CommonType!(float, int) == void));
static assert(is(CommonType!(bool, const bool) == bool));
static assert(is(CommonType!(int, bool) == void));
static assert(is(CommonType!(int, void) == void));
static assert(is(CommonType!(Object, void*) == void));
class A
{
}
static assert(is(CommonType!(A, Object) == Object));
static assert(is(CommonType!(const(A)*, Object*) == const(Object)*));
static assert(is(CommonType!(A, typeof(null)) == A));
class B : A
{
}
class C : A
{
}
static assert(is(CommonType!(B, C) == A));
static struct S
{
int opCast(T : int)()
{
return 1;
}
}
static assert(is(CommonType!(S, int) == void));
static assert(is(CommonType!(const S, S) == const S));
}
/**
* 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)));
}

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

@ -0,0 +1,228 @@
/* 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.
* 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.comparison;
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));
}
}
@nogc nothrow @safe unittest
{
version (linux)
{
assert(equal(indexToName(1)[], "lo"));
}
else version (Windows)
{
assert(equal(indexToName(1)[], "loopback_0"));
}
else
{
assert(equal(indexToName(1)[], "lo0"));
}
assert(indexToName(uint.max).empty);
}

245
source/tanya/net/inet.d Normal file
View File

@ -0,0 +1,245 @@
/* 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/. */
/**
* Internet utilities.
*
* Copyright: Eugene Wissner 2016-2018.
* 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/inet.d,
* tanya/net/inet.d)
*/
module tanya.net.inet;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range;
/**
* Represents an unsigned integer as an $(D_KEYWORD ubyte) range.
*
* The range is bidirectional. The byte order is always big-endian.
*
* It can accept any unsigned integral type but the value should fit
* in $(D_PARAM L) bytes.
*
* Params:
* L = Desired range length.
*/
struct NetworkOrder(uint L)
if (L > ubyte.sizeof && L <= ulong.sizeof)
{
static if (L > uint.sizeof)
{
private alias StorageType = ulong;
}
else static if (L > ushort.sizeof)
{
private alias StorageType = uint;
}
else static if (L > ubyte.sizeof)
{
private alias StorageType = ushort;
}
else
{
private alias StorageType = ubyte;
}
private StorageType value;
private size_t size = L;
invariant
{
assert(this.size <= L);
}
/**
* Constructs a new range.
*
* $(D_PARAM T) can be any unsigned type but $(D_PARAM value) cannot be
* larger than the maximum can be stored in $(D_PARAM L) bytes. Otherwise
* an assertion failure will be caused.
*
* Params:
* T = Value type.
* value = The value should be represented by this range.
*
* Precondition: $(D_INLINECODE value <= (2 ^^ (L * 8)) - 1).
*/
this(T)(T value)
if (isUnsigned!T)
in
{
assert(value <= (2 ^^ (L * 8)) - 1);
}
do
{
this.value = value & StorageType.max;
}
/**
* Returns: LSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
@property ubyte back() const
in
{
assert(this.length > 0);
}
do
{
return this.value & 0xff;
}
/**
* Returns: MSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
@property ubyte front() const
in
{
assert(this.length > 0);
}
do
{
return (this.value >> ((this.length - 1) * 8)) & 0xff;
}
/**
* Eliminates the LSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
void popBack()
in
{
assert(this.length > 0);
}
do
{
this.value >>= 8;
--this.size;
}
/**
* Eliminates the MSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
void popFront()
in
{
assert(this.length > 0);
}
do
{
this.value &= StorageType.max >> ((StorageType.sizeof - this.length) * 8);
--this.size;
}
/**
* Returns: Copy of this range.
*/
typeof(this) save() const
{
return this;
}
/**
* Returns: Whether the range is empty.
*/
@property bool empty() const
{
return this.length == 0;
}
/**
* Returns: Byte length.
*/
@property size_t length() const
{
return this.size;
}
}
///
@nogc nothrow pure @safe unittest
{
auto networkOrder = NetworkOrder!3(0xae34e2u);
assert(!networkOrder.empty);
assert(networkOrder.front == 0xae);
networkOrder.popFront();
assert(networkOrder.length == 2);
assert(networkOrder.front == 0x34);
assert(networkOrder.back == 0xe2);
networkOrder.popBack();
assert(networkOrder.length == 1);
assert(networkOrder.front == 0x34);
assert(networkOrder.front == 0x34);
networkOrder.popFront();
assert(networkOrder.empty);
}
// Static tests
@nogc nothrow pure @safe unittest
{
static assert(isBidirectionalRange!(NetworkOrder!4));
static assert(isBidirectionalRange!(NetworkOrder!8));
static assert(!is(NetworkOrder!9));
static assert(!is(NetworkOrder!1));
}
/**
* Converts the $(D_KEYWORD ubyte) input range $(D_PARAM range) to
* $(D_PARAM T).
*
* The byte order of $(D_PARAM r) is assumed to be big-endian. The length
* cannot be larger than $(D_INLINECODE T.sizeof). Otherwise an assertion
* failure will be caused.
*
* Params:
* T = Desired return type.
* R = Range type.
* range = Input range.
*
* Returns: Integral representation of $(D_PARAM range) with the host byte
* order.
*/
T toHostOrder(T = size_t, R)(R range)
if (isInputRange!R
&& !isInfinite!R
&& is(Unqual!(ElementType!R) == ubyte)
&& isUnsigned!T)
{
T ret;
ushort pos = T.sizeof * 8;
for (; !range.empty && range.front == 0; pos -= 8, range.popFront())
{
}
for (; !range.empty; range.popFront())
{
assert(pos != 0);
pos -= 8;
ret |= (cast(T) range.front) << pos;
}
return ret >> pos;
}
///
@nogc nothrow pure @safe unittest
{
const value = 0xae34e2u;
auto networkOrder = NetworkOrder!4(value);
assert(networkOrder.toHostOrder() == value);
}

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Network programming.
*
* Copyright: Eugene Wissner 2017-2018.
* 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/package.d,
* tanya/net/package.d)
*/
module tanya.net;
public import tanya.net.inet;
public import tanya.net.uri;

500
source/tanya/net/uri.d Normal file
View File

@ -0,0 +1,500 @@
/* 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/. */
/**
* URL parser.
*
* Copyright: Eugene Wissner 2017-2018.
* 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/uri.d,
* tanya/net/uri.d)
*/
module tanya.net.uri;
import tanya.conv;
import tanya.encoding.ascii;
import tanya.memory;
version (unittest)
{
import tanya.test.assertion;
}
/**
* Thrown if an invalid URI was specified.
*/
final class URIException : 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 nothrow pure @safe
{
super(msg, file, line, next);
}
}
/**
* A Unique Resource Locator.
*/
struct URL
{
/// The URL scheme.
const(char)[] scheme;
/// The username.
const(char)[] user;
/// The password.
const(char)[] pass;
/// The hostname.
const(char)[] host;
/// The port number.
ushort port;
/// The path.
const(char)[] path;
/// The query string.
const(char)[] query;
/// The anchor.
const(char)[] fragment;
/**
* Attempts to parse an URL from a string.
* Output string data (scheme, user, etc.) are just slices of input string
* (i.e., no memory allocation and copying).
*
* Params:
* source = The string containing the URL.
*
* Throws: $(D_PSYMBOL URIException) if the URL is malformed.
*/
this(const char[] source) @nogc pure
{
ptrdiff_t pos = -1, endPos = source.length, start;
foreach (i, ref c; source)
{
if (pos == -1 && c == ':')
{
pos = i;
}
if (endPos == source.length && (c == '?' || c == '#'))
{
endPos = i;
}
}
// Check if the colon is a part of the scheme or the port and parse
// the appropriate part.
if (source.length > 1 && source[0] == '/' && source[1] == '/')
{
// Relative scheme.
start = 2;
}
else if (pos > 0)
{
// Validate scheme:
// [ toLower(alpha) | digit | "+" | "-" | "." ]
foreach (ref c; source[0 .. pos])
{
if (!c.isAlphaNum && c != '+' && c != '-' && c != '.')
{
goto ParsePath;
}
}
if (source.length == pos + 1) // only "scheme:" is available.
{
this.scheme = source[0 .. $ - 1];
return;
}
else if (source.length > pos + 1 && source[pos + 1] == '/')
{
this.scheme = source[0 .. pos];
if (source.length > pos + 2 && source[pos + 2] == '/')
{
start = pos + 3;
if (source.length <= start)
{
// Only "scheme://" is available.
return;
}
if (this.scheme == "file" && source[start] == '/')
{
// Windows drive letters.
if (source.length - start > 2
&& source[start + 2] == ':')
{
++start;
}
goto ParsePath;
}
}
else
{
start = pos + 1;
goto ParsePath;
}
}
else if (!parsePort(source[pos .. $]))
{
// Schemas like mailto: and zlib: may not have any slash after
// them.
this.scheme = source[0 .. pos];
start = pos + 1;
goto ParsePath;
}
}
else if (pos == 0 && parsePort(source[pos .. $]))
{
// An URL shouldn't begin with a port number.
throw defaultAllocator.make!URIException("URL begins with port");
}
else
{
goto ParsePath;
}
// Parse host.
pos = -1;
for (ptrdiff_t i = start; i < source.length; ++i)
{
if (source[i] == '@')
{
pos = i;
}
else if (source[i] == '/')
{
endPos = i;
break;
}
}
// Check for login and password.
if (pos != -1)
{
// *( unreserved / pct-encoded / sub-delims / ":" )
foreach (i, c; source[start .. pos])
{
if (c == ':')
{
if (this.user is null)
{
this.user = source[start .. start + i];
this.pass = source[start + i + 1 .. pos];
}
}
else if (!c.isAlpha() &&
!c.isDigit() &&
c != '!' &&
c != ';' &&
c != '=' &&
c != '_' &&
c != '~' &&
!(c >= '$' && c <= '.'))
{
this.scheme = this.user = this.pass = null;
throw make!URIException(defaultAllocator,
"Restricted characters in user information");
}
}
if (this.user is null)
{
this.user = source[start .. pos];
}
start = ++pos;
}
pos = endPos;
if (endPos <= 1 || source[start] != '[' || source[endPos - 1] != ']')
{
// Short circuit portscan.
// IPv6 embedded address.
for (ptrdiff_t i = endPos - 1; i >= start; --i)
{
if (source[i] == ':')
{
pos = i;
if (this.port == 0 && !parsePort(source[i .. endPos]))
{
this.scheme = this.user = this.pass = null;
throw defaultAllocator.make!URIException("Invalid port");
}
break;
}
}
}
// Check if we have a valid host, if we don't reject the string as URL.
if (pos <= start)
{
this.scheme = this.user = this.pass = null;
throw defaultAllocator.make!URIException("Invalid host");
}
this.host = source[start .. pos];
if (endPos == source.length)
{
return;
}
start = endPos;
ParsePath:
endPos = source.length;
pos = -1;
foreach (i, ref c; source[start .. $])
{
if (c == '?' && pos == -1)
{
pos = start + i;
}
else if (c == '#')
{
endPos = start + i;
break;
}
}
if (pos == -1)
{
pos = endPos;
}
if (pos > start)
{
this.path = source[start .. pos];
}
if (endPos >= ++pos)
{
this.query = source[pos .. endPos];
}
if (++endPos <= source.length)
{
this.fragment = source[endPos .. $];
}
}
/*
* Attempts to parse and set the port.
*
* Params:
* port = String beginning with a colon followed by the port number and
* an optional path (query string and/or fragment), like:
* `:12345/some_path` or `:12345`.
*
* Returns: Whether the port could be found.
*/
private bool parsePort(const(char)[] port) @nogc nothrow pure @safe
{
auto unparsed = port[1 .. $];
auto parsed = readIntegral!ushort(unparsed);
if (unparsed.length == 0 || unparsed[0] == '/')
{
this.port = parsed;
return true;
}
return false;
}
}
///
@nogc pure @system unittest
{
auto u = URL("example.org");
assert(u.path == "example.org");
u = URL("relative/path");
assert(u.path == "relative/path");
// Host and scheme
u = URL("https://example.org");
assert(u.scheme == "https");
assert(u.host == "example.org");
assert(u.path is null);
assert(u.port == 0);
assert(u.fragment is null);
// With user and port and path
u = URL("https://hilary:putnam@example.org:443/foo/bar");
assert(u.scheme == "https");
assert(u.host == "example.org");
assert(u.path == "/foo/bar");
assert(u.port == 443);
assert(u.user == "hilary");
assert(u.pass == "putnam");
assert(u.fragment is null);
// With query string
u = URL("https://example.org/?login=true");
assert(u.scheme == "https");
assert(u.host == "example.org");
assert(u.path == "/");
assert(u.query == "login=true");
assert(u.fragment is null);
// With query string and fragment
u = URL("https://example.org/?login=false#label");
assert(u.scheme == "https");
assert(u.host == "example.org");
assert(u.path == "/");
assert(u.query == "login=false");
assert(u.fragment == "label");
u = URL("redis://root:password@localhost:2201/path?query=value#fragment");
assert(u.scheme == "redis");
assert(u.user == "root");
assert(u.pass == "password");
assert(u.host == "localhost");
assert(u.port == 2201);
assert(u.path == "/path");
assert(u.query == "query=value");
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"));
}
@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.
*
* Params:
* T = "scheme", "host", "port", "user", "pass", "path", "query",
* "fragment".
* source = The string containing the URL.
*
* Returns: Requested URL component.
*/
auto parseURL(string T)(const char[] source)
if (T == "scheme"
|| T == "host"
|| T == "user"
|| T == "pass"
|| T == "path"
|| T == "query"
|| T == "fragment"
|| T == "port")
{
auto ret = URL(source);
return mixin("ret." ~ T);
}
/// ditto
URL parseURL(const char[] source) @nogc pure
{
return URL(source);
}
///
@nogc pure @system unittest
{
auto u = parseURL("http://example.org:5326");
assert(u.scheme == parseURL!"scheme"("http://example.org:5326"));
assert(u.host == parseURL!"host"("http://example.org:5326"));
assert(u.user == parseURL!"user"("http://example.org:5326"));
assert(u.pass == parseURL!"pass"("http://example.org:5326"));
assert(u.path == parseURL!"path"("http://example.org:5326"));
assert(u.query == parseURL!"query"("http://example.org:5326"));
assert(u.fragment == parseURL!"fragment"("http://example.org:5326"));
assert(u.port == parseURL!"port"("http://example.org:5326"));
}

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

419
source/tanya/os/error.d Normal file
View File

@ -0,0 +1,419 @@
/* 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 a portable way of using operating system error codes.
*
* Copyright: Eugene Wissner 2017-2018.
* 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,
* tanya/os/error.d)
*/
module tanya.os.error;
import tanya.meta.trait;
// Socket API error.
private template SAError(int posix, int wsa = posix)
{
version (Windows)
{
enum SAError = 10000 + wsa;
}
else
{
alias SAError = posix;
}
}
// Error for Windows and Posix separately.
private template NativeError(int posix, int win)
{
version (Windows)
{
alias NativeError = win;
}
else
{
alias NativeError = posix;
}
}
version (Windows)
{
private enum eProtocolError = -71;
}
else version (OpenBSD)
{
private enum eProtocolError = -71;
}
else
{
private enum eProtocolError = 71;
}
/**
* System error code.
*/
struct ErrorCode
{
/**
* Error code numbers.
*/
enum ErrorNo : int
{
/// The operation completed successfully.
success = 0,
/// Operation not permitted.
noPermission = NativeError!(1, 5),
/// Interrupted system call.
interrupted = SAError!4,
/// Bad file descriptor.
badDescriptor = SAError!9,
/// An operation on a non-blocking socket would block.
wouldBlock = SAError!(11, 35),
/// Out of memory.
noMemory = NativeError!(12, 14),
/// Access denied.
accessDenied = SAError!13,
/// An invalid pointer address detected.
fault = SAError!14,
/// No such device.
noSuchDevice = NativeError!(19, 20),
/// An invalid argument was supplied.
invalidArgument = SAError!22,
/// The limit on the number of open file descriptors.
tooManyDescriptors = NativeError!(23, 331),
/// The limit on the number of open file descriptors.
noDescriptors = SAError!24,
/// Broken pipe.
brokenPipe = NativeError!(32, 109),
/// The name was too long.
nameTooLong = SAError!(36, 63),
/// A socket operation was attempted on a non-socket.
notSocket = SAError!(88, 38),
/// Protocol error.
protocolError = eProtocolError,
/// Message too long.
messageTooLong = SAError!(90, 40),
/// Wrong protocol type for socket.
wrongProtocolType = SAError!(91, 41),
/// Protocol not available.
noProtocolOption = SAError!(92, 42),
/// 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
/// address family.
socketNotSupported = SAError!(94, 44),
/// The address family is no supported by the protocol family.
operationNotSupported = SAError!(95, 45),
/// Address family specified is not supported.
addressFamilyNotSupported = SAError!(97, 47),
/// Address already in use.
addressInUse = SAError!(98, 48),
/// The network is not available.
networkDown = SAError!(100, 50),
/// No route to host.
networkUnreachable = SAError!(101, 51),
/// Network dropped connection because of reset.
networkReset = SAError!(102, 52),
/// The connection has been aborted.
connectionAborted = SAError!(103, 53),
/// Connection reset by peer.
connectionReset = SAError!(104, 54),
/// No free buffer space is available for a socket operation.
noBufferSpace = SAError!(105, 55),
/// Transport endpoint is already connected.
alreadyConnected = SAError!(106, 56),
/// Transport endpoint is not connected.
notConnected = SAError!(107, 57),
/// Cannot send after transport endpoint shutdown.
shutdown = SAError!(108, 58),
/// The connection attempt timed out, or the connected host has failed
/// to respond.
timedOut = SAError!(110, 60),
/// Connection refused.
connectionRefused = SAError!(111, 61),
/// Host is down.
hostDown = SAError!(112, 64),
/// No route to host.
hostUnreachable = SAError!(113, 65),
/// Operation already in progress.
alreadyStarted = SAError!(114, 37),
/// Operation now in progress.
inProgress = SAError!(115, 36),
/// Operation cancelled.
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) @nogc nothrow pure @safe
{
this.value_ = value;
}
///
@nogc nothrow pure @safe unittest
{
ErrorCode ec;
assert(ec == ErrorCode.success);
ec = ErrorCode.fault;
assert(ec == ErrorCode.fault);
}
/**
* Resets this $(D_PSYMBOL ErrorCode) to default
* ($(D_PSYMBOL ErrorCode.success)).
*/
void reset() @nogc nothrow pure @safe
{
this.value_ = ErrorNo.success;
}
///
@nogc nothrow pure @safe unittest
{
auto ec = ErrorCode(ErrorCode.fault);
assert(ec == ErrorCode.fault);
ec.reset();
assert(ec == ErrorCode.success);
}
/**
* Returns: Numeric error code.
*/
ErrorNo opCast(T : ErrorNo)() const
{
return this.value_;
}
/// ditto
ErrorNo opCast(T : int)() const
{
return this.value_;
}
///
@nogc nothrow pure @safe unittest
{
ErrorCode ec = ErrorCode.fault;
auto errorNo = cast(ErrorCode.ErrorNo) ec;
assert(errorNo == ErrorCode.fault);
static assert(is(typeof(cast(int) ec)));
}
/**
* Assigns another error code or error code number.
*
* Params:
* that = Numeric error code.
*
* Returns: $(D_KEYWORD this).
*/
ref ErrorCode opAssign(const ErrorNo that) @nogc nothrow pure @safe
{
this.value_ = that;
return this;
}
/// ditto
ref ErrorCode opAssign(const ErrorCode that) @nogc nothrow pure @safe
{
this.value_ = that.value_;
return this;
}
///
@nogc nothrow pure @safe unittest
{
ErrorCode ec;
assert(ec == ErrorCode.success);
ec = ErrorCode.fault;
assert(ec == ErrorCode.fault);
}
///
@nogc nothrow pure @safe unittest
{
auto ec1 = ErrorCode(ErrorCode.fault);
ErrorCode ec2;
assert(ec2 == ErrorCode.success);
ec2 = ec1;
assert(ec1 == ec2);
}
/**
* Equality with another error code or error code number.
*
* Params:
* that = Numeric error code.
*
* Returns: Whether $(D_KEYWORD this) and $(D_PARAM that) are equal.
*/
bool opEquals(const ErrorNo that) const @nogc nothrow pure @safe
{
return this.value_ == that;
}
/// ditto
bool opEquals(const ErrorCode that) const @nogc nothrow pure @safe
{
return this.value_ == that.value_;
}
///
@nogc nothrow pure @safe unittest
{
ErrorCode ec1 = ErrorCode.fault;
ErrorCode ec2 = ErrorCode.accessDenied;
assert(ec1 != ec2);
assert(ec1 != ErrorCode.accessDenied);
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");
}
@nogc nothrow pure @safe unittest
{
ErrorCode ec = cast(ErrorCode.ErrorNo) -1;
assert(ec.toString() is null);
}
private ErrorNo value_ = ErrorNo.success;
alias ErrorNo this;
}

18
source/tanya/os/package.d Normal file
View File

@ -0,0 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This package provides platform-independent interfaces to operating system
* functionality.
*
* Copyright: Eugene Wissner 2017-2018.
* 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,
* tanya/os/package.d)
*/
module tanya.os;
public import tanya.os.error;

View File

@ -0,0 +1,230 @@
/* 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.functional;
import tanya.meta.trait;
import tanya.range;
version (unittest)
{
static struct Container
{
void insertBack(const(char)[])
{
}
}
}
private mixin template InserterCtor()
{
private Container* container;
private this(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);
}
@nogc nothrow pure @safe unittest
{
auto func()()
{
Container container;
return backInserter(container);
}
static assert(!is(typeof(func!())));
}
@nogc nothrow pure @safe unittest
{
Container container;
static assert(isOutputRange!(typeof(backInserter(container)), string));
}
/**
* 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(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[]));
}

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

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,61 @@
/* 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.
* 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/linux/syscall.d,
* tanya/sys/linux/syscall.d)
*/
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,78 @@
/* 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.
* 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/posix/ioctl.d,
* tanya/sys/posix/ioctl.d)
*/
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,31 @@
/* 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.
* 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/posix/mman.d,
* tanya/sys/posix/mman.d)
*/
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,27 @@
/* 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.
* 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/posix/net/if_.d,
* tanya/sys/posix/net/if_.d)
*/
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,152 @@
/* 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.
* 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/posix/socket.d,
* tanya/sys/posix/socket.d)
*/
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

@ -0,0 +1,66 @@
/* 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/. */
/**
* Base type definitions and aliases.
*
* This module doesn't provide aliases for all types used by Windows, but only
* for types that can vary on different platforms. For example there is no
* need to define `INT32` alias for D, since $(D_KEYWORD int) is always a
* 32-bit signed integer. But `int` and its Windows alias `INT` is not the
* same on all platforms in C, so its size can be something differen than
* 32 bit, therefore an $(D_PSYMBOL INT) alias is available in this module.
* $(D_PARAM TCHAR) can be a $(D_KEYWORD char) if Unicode isn't supported or
* $(D_KEYWORD wchar) if Unicode is supported, so $(D_PSYMBOL TCHAR) is
* defined here.
* Also aliases for specific types like $(D_PSYMBOL SOCKET) are defined here.
*
* Copyright: Eugene Wissner 2017-2018.
* 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;
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;
alias WORD = ushort;
alias INT = int;
alias UINT = uint;
alias LONG = int;
alias ULONG = uint;
alias DWORD = uint;
alias LONGLONG = long; // Or double.
alias ULONGLONG = ulong; // Or double.
alias DWORDLONG = ulong;
alias FLOAT = float;
alias BOOL = int;
alias BOOLEAN = BYTE;
alias HANDLE = void*;
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,30 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Copyright: Eugene Wissner 2018.
* 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/ifdef.d,
* tanya/sys/windows/ifdef.d)
*/
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,39 @@
/* 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.
* 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/iphlpapi.d,
* tanya/sys/windows/iphlpapi.d)
*/
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

@ -0,0 +1,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Copyright: Eugene Wissner 2017-2018.
* 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.ifdef;
public import tanya.sys.windows.iphlpapi;
public import tanya.sys.windows.winbase;
public import tanya.sys.windows.winsock2;

View File

@ -0,0 +1,55 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Definitions from winbase.h.
*
* Copyright: Eugene Wissner 2017-2018.
* 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;
version (Windows):
public import tanya.sys.windows.def;
struct OVERLAPPED
{
size_t Internal;
size_t InternalHigh;
union
{
struct
{
DWORD Offset;
DWORD OffsetHigh;
}
void* Pointer;
}
HANDLE hEvent;
}
extern(Windows)
HANDLE CreateIoCompletionPort(HANDLE FileHandle,
HANDLE ExistingCompletionPort,
size_t CompletionKey,
DWORD NumberOfConcurrentThreads)
nothrow @system @nogc;
extern(Windows)
BOOL GetQueuedCompletionStatus(HANDLE CompletionPort,
DWORD* lpNumberOfBytes,
size_t* lpCompletionKey,
OVERLAPPED** lpOverlapped,
DWORD dwMilliseconds) nothrow @system @nogc;
extern(Windows)
BOOL GetOverlappedResult(HANDLE hFile,
OVERLAPPED* lpOverlapped,
DWORD* lpNumberOfBytesTransferred,
BOOL bWait) nothrow @system @nogc;

View File

@ -0,0 +1,219 @@
/* 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/. */
/**
* Definitions from winsock2.h, ws2def.h and MSWSock.h.
*
* Copyright: Eugene Wissner 2017-2018.
* 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;
version (Windows):
public import tanya.sys.windows.def;
public import tanya.sys.windows.winbase;
alias SOCKET = size_t;
enum SOCKET INVALID_SOCKET = ~0;
enum SOCKET_ERROR = -1;
enum
{
IOC_UNIX = 0x00000000,
IOC_WS2 = 0x08000000,
IOC_PROTOCOL = 0x10000000,
IOC_VOID = 0x20000000, // No parameters.
IOC_OUT = 0x40000000, // Copy parameters back.
IOC_IN = 0x80000000, // Copy parameters into.
IOC_VENDOR = 0x18000000,
IOC_WSK = (IOC_WS2 | 0x07000000), // _WIN32_WINNT >= 0x0600.
IOC_INOUT = (IOC_IN | IOC_OUT), // Copy parameter into and get back.
}
template _WSAIO(int x, int y)
{
enum _WSAIO = IOC_VOID | x | y;
}
template _WSAIOR(int x, int y)
{
enum _WSAIOR = IOC_OUT | x | y;
}
template _WSAIOW(int x, int y)
{
enum _WSAIOW = IOC_IN | x | y;
}
template _WSAIORW(int x, int y)
{
enum _WSAIORW = IOC_INOUT | x | y;
}
alias SIO_ASSOCIATE_HANDLE = _WSAIOW!(IOC_WS2, 1);
alias SIO_ENABLE_CIRCULAR_QUEUEING = _WSAIO!(IOC_WS2, 2);
alias SIO_FIND_ROUTE = _WSAIOR!(IOC_WS2, 3);
alias SIO_FLUSH = _WSAIO!(IOC_WS2, 4);
alias SIO_GET_BROADCAST_ADDRESS = _WSAIOR!(IOC_WS2, 5);
alias SIO_GET_EXTENSION_FUNCTION_POINTER = _WSAIORW!(IOC_WS2, 6);
alias SIO_GET_QOS = _WSAIORW!(IOC_WS2, 7);
alias SIO_GET_GROUP_QOS = _WSAIORW!(IOC_WS2, 8);
alias SIO_MULTIPOINT_LOOPBACK = _WSAIOW!(IOC_WS2, 9);
alias SIO_MULTICAST_SCOPE = _WSAIOW!(IOC_WS2, 10);
alias SIO_SET_QOS = _WSAIOW!(IOC_WS2, 11);
alias SIO_SET_GROUP_QOS = _WSAIOW!(IOC_WS2, 12);
alias SIO_TRANSLATE_HANDLE = _WSAIORW!(IOC_WS2, 13);
alias SIO_ROUTING_INTERFACE_QUERY = _WSAIORW!(IOC_WS2, 20);
alias SIO_ROUTING_INTERFACE_CHANGE = _WSAIOW!(IOC_WS2, 21);
alias SIO_ADDRESS_LIST_QUERY = _WSAIOR!(IOC_WS2, 22);
alias SIO_ADDRESS_LIST_CHANGE = _WSAIO!(IOC_WS2, 23);
alias SIO_QUERY_TARGET_PNP_HANDLE = _WSAIOR!(IOC_WS2, 24);
alias SIO_NSP_NOTIFY_CHANGE = _WSAIOW!(IOC_WS2, 25);
alias GROUP = uint;
enum
{
WSA_FLAG_OVERLAPPED = 0x01,
WSA_FLAG_MULTIPOINT_C_ROOT = 0x02,
WSA_FLAG_MULTIPOINT_C_LEAF = 0x04,
WSA_FLAG_MULTIPOINT_D_ROOT = 0x08,
WSA_FLAG_MULTIPOINT_D_LEAF = 0x10,
WSA_FLAG_ACCESS_SYSTEM_SECURITY = 0x40,
WSA_FLAG_NO_HANDLE_INHERIT = 0x80,
WSA_FLAG_REGISTERED_IO = 0x100,
}
enum MAX_PROTOCOL_CHAIN = 7;
enum BASE_PROTOCOL = 1;
enum LAYERED_PROTOCOL = 0;
enum WSAPROTOCOL_LEN = 255;
struct WSAPROTOCOLCHAIN
{
int ChainLen;
DWORD[MAX_PROTOCOL_CHAIN] ChainEntries;
}
struct WSABUF
{
ULONG len;
CHAR* buf;
}
struct WSAPROTOCOL_INFO
{
DWORD dwServiceFlags1;
DWORD dwServiceFlags2;
DWORD dwServiceFlags3;
DWORD dwServiceFlags4;
DWORD dwProviderFlags;
GUID ProviderId;
DWORD dwCatalogEntryId;
WSAPROTOCOLCHAIN ProtocolChain;
int iVersion;
int iAddressFamily;
int iMaxSockAddr;
int iMinSockAddr;
int iSocketType;
int iProtocol;
int iProtocolMaxOffset;
int iNetworkByteOrder;
int iSecurityScheme;
DWORD dwMessageSize;
DWORD dwProviderReserved;
TCHAR[WSAPROTOCOL_LEN + 1] szProtocol;
}
const GUID WSAID_GETACCEPTEXSOCKADDRS = {
0xb5367df2, 0xcbac, 0x11cf,
[0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92],
};
const GUID WSAID_ACCEPTEX = {
0xb5367df1, 0xcbac, 0x11cf,
[0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92],
};
alias LPWSAOVERLAPPED_COMPLETION_ROUTINE = void function(DWORD dwError,
DWORD cbTransferred,
OVERLAPPED* lpOverlapped,
DWORD dwFlags) nothrow @nogc;
extern(Windows)
SOCKET WSASocket(int af,
int type,
int protocol,
WSAPROTOCOL_INFO* lpProtocolInfo,
GROUP g,
DWORD dwFlags) nothrow @system @nogc;
extern(Windows)
int WSARecv(SOCKET s,
WSABUF* lpBuffers,
DWORD dwBufferCount,
DWORD* lpNumberOfBytesRecvd,
DWORD* lpFlags,
OVERLAPPED* lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
nothrow @system @nogc;
extern(Windows)
int WSASend(SOCKET s,
WSABUF* lpBuffers,
DWORD dwBufferCount,
DWORD* lpNumberOfBytesRecvd,
DWORD lpFlags,
OVERLAPPED* lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
nothrow @system @nogc;
extern(Windows)
int WSAIoctl(SOCKET s,
uint dwIoControlCode,
void* lpvInBuffer,
uint cbInBuffer,
void* lpvOutBuffer,
uint cbOutBuffer,
uint* lpcbBytesReturned,
OVERLAPPED* lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
nothrow @system @nogc;
alias ADDRESS_FAMILY = USHORT;
struct SOCKADDR
{
ADDRESS_FAMILY sa_family; // Address family.
CHAR[14] sa_data; // Up to 14 bytes of direct address.
}
alias LPFN_GETACCEPTEXSOCKADDRS = void function(void*,
DWORD,
DWORD,
DWORD,
SOCKADDR**,
INT*,
SOCKADDR**,
INT*) nothrow @nogc;
alias LPFN_ACCEPTEX = extern(Windows) BOOL function(SOCKET,
SOCKET,
void*,
DWORD,
DWORD,
DWORD,
DWORD*,
OVERLAPPED*) @nogc nothrow;
enum
{
SO_MAXDG = 0x7009,
SO_MAXPATHDG = 0x700A,
SO_UPDATE_ACCEPT_CONTEXT = 0x700B,
SO_CONNECT_TIME = 0x700C,
SO_UPDATE_CONNECT_CONTEXT = 0x7010,
}

View File

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

View File

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

373
source/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.
* 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
{
}
}
}

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