366 Commits

Author SHA1 Message Date
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
ab930657b6 Queue: Leave only enqueue/dequeue/empty/opApply 2017-01-11 18:24:50 +01:00
291920b479 Vector constructors for initializing from a vector 2017-01-10 06:34:53 +01:00
999c9bdb0f Vector: remove core.stdc.string import 2017-01-09 19:52:39 +01:00
405b6d9f9f Accept only ranges for slicing assignment 2017-01-09 19:32:51 +01:00
87b74b2542 Fix reallocating the vector 2017-01-09 17:03:09 +01:00
d6514cb515 Fix Ddoc 2017-01-07 17:53:57 +01:00
976eb4bfbc Add downloads button 2017-01-07 15:25:05 +01:00
fb843e3473 Fix #3 2017-01-07 09:30:42 +01:00
f3d48234c0 MmapPool: add expand and empty methods. 2017-01-06 23:12:19 +01:00
254b881da6 Fix block size calculation 2017-01-06 11:56:54 +01:00
8e0b742748 MmapPool: Merge blocks on deallocation if possible 2017-01-05 14:25:54 +01:00
a35e04c049 Don't throw in the allocator, return null 2017-01-05 07:42:23 +01:00
4271c8583e Remove static constructor from the MmapPool 2017-01-05 07:35:29 +01:00
e27d0fe58c Fix Vector.remove not destroying from the end copied elements 2017-01-04 20:37:55 +01:00
67952dabdb Implement Vector.remove 2017-01-03 13:21:19 +01:00
b8d5d4c2bd Fix template condition for Vector.insertBack 2017-01-03 10:03:28 +01:00
b6413823cd Add opEquals for all combinations of vector ranges 2017-01-02 17:33:01 +01:00
48e355b87f Vector: allow insert multiple items in insertBack 2017-01-02 12:47:41 +01:00
b3f4ea572e Vector: Use opEquals if defined to compare items 2017-01-02 06:59:05 +01:00
c73e704421 Fix constness of Vector range, optimizing 2017-01-01 02:51:49 +01:00
0561e96f21 Fix build with 2.071.2 2016-12-28 07:57:36 +01:00
86d87430da Fix socket build on Windows 2016-12-25 12:54:04 +01:00
0156c5a883 Don't allocate watcher queue on the heap 2016-12-25 00:54:05 +01:00
c966b42ac3 Fix FreeBSD build 2016-12-24 22:25:34 +01:00
200fff3714 Fix #1 2016-12-22 22:05:48 +01:00
28755b4d01 Rename module traits into enums 2016-12-22 22:05:06 +01:00
8bd6a14988 Fix issue going out of the range with back() 2016-12-22 22:01:45 +01:00
b41dcc9f37 Fix compatibility issue with dmd 2.071 2016-12-22 22:01:11 +01:00
38addb7a5b Add support for pow for big integers 2016-12-22 21:51:16 +01:00
f7fb89fed0 Move random.d into math submodule 2016-12-22 21:50:33 +01:00
e32af2d09e Add scalar type template parameter for buffers 2016-12-19 21:24:28 +01:00
f1bc4dc2e2 Add length and opCmp to the Queue 2016-12-19 16:33:16 +01:00
40857e69b7 Add capacity capabilities to the vector 2016-12-18 18:48:25 +01:00
c1fb89af99 Implement insertion into the vector 2016-12-15 15:00:06 +01:00
061cd6264b Use auto ref for templated overloaded functions 2016-12-13 10:59:05 +01:00
f437dafa6b Fix dispose for structs 2016-12-13 10:58:11 +01:00
54d0597657 Use resizeArray instead of expand/shrinkArray 2016-12-13 10:57:12 +01:00
ab9f96e0c7 Replace class Queue with the struct Queue 2016-12-13 10:56:29 +01:00
711855474c Remove unused buffer interface 2016-12-13 10:54:27 +01:00
b20f367aa8 Array support for refCounted factory function 2016-12-11 11:42:09 +01:00
a2dadda511 Fix subtraction of numbers with different signs 2016-12-08 18:30:22 +01:00
77dca31261 Add license info 2016-12-08 15:07:58 +01:00
b87aed4395 Add travis to README 2016-12-08 15:00:09 +01:00
42bbb3b023 Add travis 2016-12-08 14:58:59 +01:00
4309a30dfe Add opBinary for the other math operations on Integer 2016-12-08 14:51:49 +01:00
9362287938 Fix error with assignin long numbers to Integer 2016-12-08 14:43:50 +01:00
78bd901339 Add short description of the packages to the README 2016-12-07 23:16:49 +01:00
c8e6d44f7b Implement own dispose 2016-12-07 11:01:51 +01:00
f75433e0e6 Implement operations on negative numbers 2016-12-06 23:22:12 +01:00
fa607141e4 Make allocator shared and fix some RefCounted bugs 2016-12-06 21:29:08 +01:00
b3fdd6fd4a Implement unary operation for multiple precision integers 2016-12-05 22:06:06 +01:00
86c08e7af6 Use RefCounted as math.mp.Integer internal storage 2016-12-04 22:51:21 +01:00
1c5796eb96 Add RefCounted 2016-12-04 14:05:53 +01:00
f7f92e7906 Switch to container.queue. Remove PendingQueue 2016-12-02 19:18:37 +01:00
1123d01e6c Implement opApply for the Queue 2016-12-02 17:31:57 +01:00
c53d319337 Implement foreach_reverse for the vector 2016-12-02 14:12:12 +01:00
7c36dbb8f0 Rename SList front property into insertFront 2016-12-02 10:50:54 +01:00
dd3becf6b7 Implement slicing for the vector 2016-12-02 10:29:30 +01:00
b78ecdf4c5 Make tanya.memory.allocator compatible with dmd < 2.072 2016-12-01 20:04:04 +01:00
a4aa5bcb2e Make opApply delegate scoped for SList 2016-12-01 20:03:21 +01:00
edd3ec4b32 Add URL parser 2016-12-01 20:02:49 +01:00
9fdcef86e7 Replace defaultAllocator with theAllocator 2016-11-30 21:54:31 +01:00
ed0eb4ac74 Fix epoll connection bugs 2016-11-30 21:53:30 +01:00
192ee20bf7 Remove shared from the allocators 2016-11-30 21:20:18 +01:00
965ca0088e Add multiple precision unsigned integer module 2016-11-30 20:24:51 +01:00
b752acdff7 Fix tanya.math module name 2016-11-30 16:48:46 +01:00
cbeb0395f9 Remove @safe from potentially unsafe code 2016-11-30 16:21:20 +01:00
5e6f8446d8 Add an abandoned notice 2016-10-08 20:45:03 +02:00
83 changed files with 26314 additions and 4567 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

12
.gitignore vendored
View File

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

33
.travis.yml Normal file
View File

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

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)

185
README.md Normal file
View File

@ -0,0 +1,185 @@
# Tanya
[![Build status](https://travis-ci.org/caraus-ecms/tanya.svg?branch=master)](https://travis-ci.org/caraus-ecms/tanya)
[![Build status](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/master?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/master)
[![codecov](https://codecov.io/gh/caraus-ecms/tanya/branch/master/graph/badge.svg)](https://codecov.io/gh/caraus-ecms/tanya)
[![Dub version](https://img.shields.io/dub/v/tanya.svg)](https://code.dlang.org/packages/tanya)
[![Dub downloads](https://img.shields.io/dub/dt/tanya.svg)](https://code.dlang.org/packages/tanya)
[![License](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
Tanya is a general purpose library for D programming language.
Its aim is to simplify the manual memory management in D and to provide a
guarantee with @nogc attribute that there are no hidden allocations on the
Garbage Collector heap. Everything in the library is usable in @nogc code.
Tanya extends Phobos functionality and provides alternative implementations for
data structures and utilities that depend on the Garbage Collector in Phobos.
* [API Documentation](https://docs.caraus.io/tanya)
* [Contribution guidelines](CONTRIBUTING.md)
## Overview
Tanya consists of the following packages and (top-level) modules:
* `algorithm`: Collection of generic algorithms.
* `async`: Event loop (epoll, kqueue and IOCP).
* `container`: Queue, Array, Singly and doubly linked lists, Buffers, UTF-8
string, Hash set.
* `conv`: This module provides functions for converting between different
types.
* `encoding`: This package provides tools to work with text encodings.
* `exception`: Common exceptions and errors.
* `format`: Formatting and conversion functions.
* `math`: Arbitrary precision integer and a set of functions.
* `memory`: Tools for manual memory management (allocators, smart pointers).
* `meta`: Template metaprogramming. This package contains utilities to acquire
type information at compile-time, to transform from one type to another. It has
also different algorithms for iterating, searching and modifying template
arguments.
* `net`: URL-Parsing, network programming.
* `network`: Socket implementation. `network` is currently under rework.
After finishing the new socket implementation will land in the `net` package and
`network` will be deprecated.
* `os`: Platform-independent interfaces to operating system functionality.
* `range`: Generic functions and templates for D ranges.
* `test`: Test suite for unittest-blocks.
* `typecons`: Templates that allow to build new types based on the available
ones.
## Basic usage
### 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);
}
```
### 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.
## Development
### Supported compilers
| DMD | GCC |
|:-------:|:--------------:|
| 2.077.0 | *gdc-5* branch |
| 2.076.1 | |
| 2.075.1 | |
### Current status
Following modules are under development:
| Feature | Branch | Build status |
|----------|:---------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| BitArray | bitvector | [![bitvector](https://travis-ci.org/caraus-ecms/tanya.svg?branch=bitvector)](https://travis-ci.org/caraus-ecms/tanya) [![bitvector](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/bitvector?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/bitvector) |
| TLS | crypto | [![crypto](https://travis-ci.org/caraus-ecms/tanya.svg?branch=crypto)](https://travis-ci.org/caraus-ecms/tanya) [![crypto](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/crypto?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/crypto) |
| File IO | io | [![io](https://travis-ci.org/caraus-ecms/tanya.svg?branch=io)](https://travis-ci.org/caraus-ecms/tanya) [![io](https://ci.appveyor.com/api/projects/status/djkmverdfsylc7ti/branch/io?svg=true)](https://ci.appveyor.com/project/belka-ew/tanya/branch/io) |
### Release management
3-week release cycle.
Deprecated features are removed after one release (in approximately 6 weeks after deprecating).
## Further characteristics
* Tanya is a native D library without any external dependencies.
* Tanya is cross-platform. The development happens on a 64-bit Linux, but it
is being tested on Windows and FreeBSD as well.
* The library isn't thread-safe yet.
## 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.077.0
arch: x64
- DC: dmd
DVersion: 2.077.0
arch: x86
- DC: dmd
DVersion: 2.076.1
arch: x64
- DC: dmd
DVersion: 2.076.1
arch: x86
- DC: dmd
DVersion: 2.075.1
arch: x64
- DC: dmd
DVersion: 2.075.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";
}
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 cmp.o: gas x64/linux/memory/cmp.S
build fill.o: gas x64/linux/memory/fill.S
build copy.o: gas x64/linux/memory/copy.S
build syscall.o: gas x64/linux/syscall.S
build tanya.a: archive syscall.o copy.o fill.o cmp.o log.o abs.o

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 _D5tanya4math8nbtheory10__T3absTfZ3absFNaNbNiNffZf
.type _D5tanya4math8nbtheory10__T3absTfZ3absFNaNbNiNffZf, @function
_D5tanya4math8nbtheory10__T3absTfZ3absFNaNbNiNffZf:
mov $0x7fffffff, %eax
movq %rax, %xmm1
andpd %xmm1, %xmm0
ret
// fabs.
.globl _D5tanya4math8nbtheory10__T3absTdZ3absFNaNbNiNfdZd
.type _D5tanya4math8nbtheory10__T3absTdZ3absFNaNbNiNfdZd, @function
_D5tanya4math8nbtheory10__T3absTdZ3absFNaNbNiNfdZd:
mov $0x7fffffffffffffff, %rax
movq %rax, %xmm1
andpd %xmm1, %xmm0
ret
// fabsl.
.globl _D5tanya4math8nbtheory10__T3absTeZ3absFNaNbNiNfeZe
.type _D5tanya4math8nbtheory10__T3absTeZ3absFNaNbNiNfeZe, @function
// Load the parameter from the stack onto FP stack, execute 'fabs' instruction
// The result is returned in ST0.
_D5tanya4math8nbtheory10__T3absTeZ3absFNaNbNiNfeZe:
fldt 0x8(%rsp)
fabs
ret

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

@ -0,0 +1,48 @@
.text
// logl.
.globl _D5tanya4math8nbtheory2lnFNaNbNiNfeZe
.type _D5tanya4math8nbtheory2lnFNaNbNiNfeZe, @function
_D5tanya4math8nbtheory2lnFNaNbNiNfeZe:
fldln2 // Put lb(e) onto the FPU stack
fldt 8(%rsp) // Put the argument onto the FPU stack
fyl2x // %st1 * lb(%st0)
ret
// log.
.globl _D5tanya4math8nbtheory2lnFNaNbNiNfdZd
.type _D5tanya4math8nbtheory2lnFNaNbNiNfdZd, @function
_D5tanya4math8nbtheory2lnFNaNbNiNfdZd:
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
// logf.
.globl _D5tanya4math8nbtheory2lnFNaNbNiNffZf
.type _D5tanya4math8nbtheory2lnFNaNbNiNffZf, @function
_D5tanya4math8nbtheory2lnFNaNbNiNffZf:
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

View File

@ -0,0 +1,67 @@
.text
/*
* cmpMemory.
*
* rdi - r1 length
* rsi - r1 data.
* rdx - r2 length.
* rcx - r2 data.
*/
.globl _D5tanya6memory2op9cmpMemoryFNaNbNixAvxAvZi
.type _D5tanya6memory2op9cmpMemoryFNaNbNixAvxAvZi, @function
_D5tanya6memory2op9cmpMemoryFNaNbNixAvxAvZi:
// Compare the lengths
cmp %rdx, %rdi
jl less
jg greater
mov %rcx, %rdi
// Check if we're aligned
cmp $0x08, %rdx
jc aligned_1
test $0x07, %edi
jz aligned_8
naligned:
cmpsb
jl less
jg greater
dec %rdx
test $0x07, %edi
jnz naligned
aligned_8:
mov %rdx, %rcx
shr $0x03, %rcx
repe cmpsq
jl less
jg greater
and $0x07, %edx
jz equal
aligned_1: // Compare the remaining bytes
mov %rdx, %rcx
repe cmpsb
jl less
jg greater
equal:
xor %rax, %rax // Return 0
jmp end
greater:
mov $0x01, %rax
jmp end
less:
mov $-0x01, %rax
end:
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,155 @@
.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
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
.globl syscall1
.type syscall1, @function
syscall1:
movq %rsi, %rax // Syscall number.
syscall
ret
.globl syscall2
.type syscall2, @function
syscall2:
// Store registers.
movq %rdi, %r8
movq %rdx, %rax // Syscall number.
// Syscall arguments.
movq %rsi, %rdi
movq %r8, %rsi
syscall
// Restore registers.
movq %rdi, %rsi
movq %r8, %rdi
ret
.globl syscall3
.type syscall3, @function
syscall3:
// Store registers.
movq %rdi, %r8
movq %rcx, %rax // Syscall number.
// Syscall arguments.
movq %rdx, %rdi
movq %r8, %rdx
syscall
// Restore registers.
movq %r8, %rdi
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="disabled"
; 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,6 +1,6 @@
{
"name": "tanya",
"description": "D library with event loop",
"description": "General purpose, @nogc library. Containers, networking, metaprogramming, memory management, utilities",
"license": "MPL-2.0",
"copyright": "(c) Eugene Wissner <info@caraus.de>",
"authors": [
@ -11,7 +11,17 @@
"configurations": [
{
"name": "library"
"name": "library",
"targetType": "library",
"versions": ["TanyaPhobos"]
},
{
"name": "native",
"targetType": "library",
"platforms": ["linux-x86_64-gdc"],
"preBuildCommands": ["ninja -C arch"],
"lflags": ["arch/tanya.a"],
"versions": ["TanyaNative"]
}
]
}

View File

@ -0,0 +1,275 @@
/* 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.
* 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.memory.op;
import tanya.meta.trait;
private void deinitialize(bool zero, T)(ref T value)
{
static if (is(T == U[S], U, size_t S))
{
foreach (ref e; value)
{
deinitialize!zero(e);
}
}
else
{
static if (isNested!T)
{
// Don't override the context pointer.
enum size_t size = T.sizeof - (void*).sizeof;
}
else
{
enum size_t size = T.sizeof;
}
static if (zero)
{
fill!0((cast(void*) &value)[0 .. size]);
}
else
{
copy(typeid(T).initializer()[0 .. size], (&value)[0 .. 1]);
}
}
}
/**
* 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");
}
body
{
static if (is(T == struct) || isStaticArray!T)
{
copy((&source)[0 .. 1], (&target)[0 .. 1]);
static if (hasElaborateCopyConstructor!T || hasElaborateDestructor!T)
{
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
{
T target = void;
moveEmplace(source, target);
return target;
}
///
@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.
* a = 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);
}

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/. */
/**
* Collection of generic algorithms.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/algorithm/package.d,
* tanya/algorithm/package.d)
*/
module tanya.algorithm;
public import tanya.algorithm.mutation;

View File

@ -3,53 +3,67 @@
* 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-2017.
* 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)
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/event/epoll.d,
* tanya/async/event/epoll.d)
*/
module tanya.async.event.epoll;
version (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;
public import core.sys.linux.epoll;
import core.sys.posix.unistd;
import core.time;
import std.algorithm.comparison;
import tanya.async.event.selector;
import tanya.async.loop;
import tanya.async.protocol;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.array;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
class EpollLoop : SelectorLoop
extern (C) nothrow @nogc
{
int epoll_create1(int flags);
int epoll_ctl (int epfd, int op, int fd, epoll_event *event);
int epoll_wait (int epfd, epoll_event *events, int maxevents, int timeout);
}
final class EpollLoop : SelectorLoop
{
protected int fd;
private epoll_event[] events;
private Array!epoll_event events;
/**
* Initializes the loop.
*/
this()
this() @nogc
{
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
{
throw MmapPool.instance.make!BadLoopException("epoll initialization failed");
throw defaultAllocator.make!BadLoopException("epoll initialization failed");
}
super();
events = MmapPool.instance.makeArray!epoll_event(maxEvents);
events = Array!epoll_event(maxEvents, MmapPool.instance);
}
/**
* Free loop internals.
* Frees loop internals.
*/
~this()
~this() @nogc
{
MmapPool.instance.dispose(events);
close(fd);
}
@ -63,12 +77,9 @@ class EpollLoop : SelectorLoop
*
* Returns: $(D_KEYWORD true) if the operation was successful.
*/
protected override bool reify(ConnectionWatcher watcher, EventMask oldEvents, EventMask events)
in
{
assert(watcher !is null);
}
body
protected override bool reify(SocketWatcher watcher,
EventMask oldEvents,
EventMask events) @nogc
{
int op = EPOLL_CTL_DEL;
epoll_event ev;
@ -97,11 +108,11 @@ class EpollLoop : SelectorLoop
/**
* Does the actual polling.
*/
protected override void poll()
protected override void poll() @nogc
{
// Don't block
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)
{
@ -114,29 +125,30 @@ class EpollLoop : SelectorLoop
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);
acceptConnections(connection);
}
else if (events[i].events & EPOLLERR)
{
kill(io, null);
kill(transport);
continue;
}
else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP))
{
auto transport = cast(SelectorStreamTransport) io.transport;
assert(transport !is null);
SocketException exception;
try
{
ptrdiff_t received;
do
{
received = transport.socket.receive(io.output[]);
io.output += received;
received = transport.socket.receive(transport.output[]);
transport.output += received;
}
while (received);
}
@ -146,18 +158,16 @@ class EpollLoop : SelectorLoop
}
if (transport.socket.disconnected)
{
kill(io, exception);
kill(transport, exception);
continue;
}
else if (io.output.length)
else if (transport.output.length)
{
swapPendings.insertBack(io);
pendings.enqueue(transport);
}
}
else if (events[i].events & EPOLLOUT)
if (events[i].events & EPOLLOUT)
{
auto transport = cast(SelectorStreamTransport) io.transport;
assert(transport !is null);
transport.writeReady = true;
if (transport.input.length)
{

View File

@ -3,59 +3,96 @@
* 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-2017.
* 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)
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/event/iocp.d,
* tanya/async/event/iocp.d)
*/
module tanya.async.event.iocp;
version (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.protocol;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.buffer;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
import core.sys.windows.basetyps;
import core.sys.windows.mswsock;
import core.sys.windows.winbase;
import core.sys.windows.windef;
import core.sys.windows.winsock2;
import tanya.sys.windows.winbase;
class IOCPStreamTransport : StreamTransport
/**
* Transport for stream sockets.
*/
final class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
{
private OverlappedConnectedSocket socket_;
private SocketException exception;
private WriteBuffer input;
private ReadBuffer!ubyte output;
private WriteBuffer!ubyte input;
private Protocol protocol_;
private bool closing;
/**
* Creates new completion port transport.
*
* Params:
* socket = Socket.
*
* Precondition: $(D_INLINECODE socket !is null)
*/
this(OverlappedConnectedSocket socket)
in
this(OverlappedConnectedSocket socket) @nogc
{
super(socket);
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
input = WriteBuffer!ubyte(8192, MmapPool.instance);
active = true;
}
/**
* Returns: Socket.
*
* Postcondition: $(D_INLINECODE socket !is null)
*/
override @property OverlappedConnectedSocket socket() pure nothrow @safe @nogc
out (socket)
{
assert(socket !is null);
}
body
{
socket_ = socket;
input = MmapPool.instance.make!WriteBuffer();
return cast(OverlappedConnectedSocket) socket_;
}
~this()
/**
* Returns $(D_PARAM true) if the transport is closing or closed.
*/
bool isClosing() const pure nothrow @safe @nogc
{
MmapPool.instance.dispose(input);
return closing;
}
@property inout(OverlappedConnectedSocket) socket() inout pure nothrow @safe @nogc
/**
* Close the transport.
*
* Buffered data will be flushed. No more data will be received.
*/
void close() pure nothrow @safe @nogc
{
return socket_;
closing = true;
}
/**
@ -64,10 +101,50 @@ class IOCPStreamTransport : StreamTransport
* Params:
* data = Data to send.
*/
void write(ubyte[] data)
void write(ubyte[] data) @nogc
{
input ~= data;
}
/**
* Returns: Application protocol.
*/
@property Protocol protocol() pure nothrow @safe @nogc
{
return protocol_;
}
/**
* Switches the protocol.
*
* The protocol is deallocated by the event loop, it should currently be
* allocated with $(D_PSYMBOL MmapPool).
*
* Params:
* protocol = Application protocol.
*
* Precondition: $(D_INLINECODE protocol !is null)
*/
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
in
{
assert(protocol !is null);
}
body
{
protocol_ = protocol;
}
/**
* Invokes the watcher callback.
*/
override void invoke() @nogc
{
if (output.length)
{
immutable empty = input.length == 0;
input ~= data;
protocol.received(output[0 .. $]);
output.clear();
if (empty)
{
SocketState overlapped;
@ -83,9 +160,17 @@ class IOCPStreamTransport : StreamTransport
}
}
}
else
{
protocol.disconnected(exception);
MmapPool.instance.dispose(protocol_);
defaultAllocator.dispose(exception);
active = false;
}
}
}
class IOCPLoop : Loop
final class IOCPLoop : Loop
{
protected HANDLE completionPort;
@ -94,14 +179,15 @@ class IOCPLoop : Loop
/**
* Initializes the loop.
*/
this()
this() @nogc
{
super();
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, null, 0, 0);
if (!completionPort)
{
throw defaultAllocator.make!BadLoopException("Creating completion port failed");
throw make!BadLoopException(defaultAllocator,
"Creating completion port failed");
}
}
@ -115,9 +201,9 @@ class IOCPLoop : Loop
*
* Returns: $(D_KEYWORD true) if the operation was successful.
*/
override protected bool reify(ConnectionWatcher watcher,
override protected bool reify(SocketWatcher watcher,
EventMask oldEvents,
EventMask events)
EventMask events) @nogc
{
SocketState overlapped;
if (!(oldEvents & Event.accept) && (events & Event.accept))
@ -127,7 +213,7 @@ class IOCPLoop : Loop
if (CreateIoCompletionPort(cast(HANDLE) socket.handle,
completionPort,
cast(ULONG_PTR) (cast(void*) watcher),
cast(size_t) (cast(void*) watcher),
0) !is completionPort)
{
return false;
@ -145,18 +231,15 @@ class IOCPLoop : Loop
return false;
}
}
if (!(oldEvents & Event.read) && (events & Event.read)
|| !(oldEvents & Event.write) && (events & Event.write))
if ((!(oldEvents & Event.read) && (events & Event.read))
|| (!(oldEvents & Event.write) && (events & Event.write)))
{
auto io = cast(IOWatcher) watcher;
assert(io !is null);
auto transport = cast(IOCPStreamTransport) io.transport;
auto transport = cast(StreamTransport) watcher;
assert(transport !is null);
if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle,
completionPort,
cast(ULONG_PTR) (cast(void*) watcher),
cast(size_t) (cast(void*) watcher),
0) !is completionPort)
{
return false;
@ -168,7 +251,7 @@ class IOCPLoop : Loop
try
{
overlapped = MmapPool.instance.make!SocketState;
transport.socket.beginReceive(io.output[], overlapped);
transport.socket.beginReceive(transport.output[], overlapped);
}
catch (SocketException e)
{
@ -181,14 +264,28 @@ class IOCPLoop : Loop
return true;
}
private void kill(StreamTransport transport,
SocketException exception = null) @nogc
in
{
assert(transport !is null);
}
body
{
transport.socket.shutdown();
defaultAllocator.dispose(transport.socket);
transport.exception = exception;
pendings.enqueue(transport);
}
/**
* Does the actual polling.
*/
override protected void poll()
override protected void poll() @nogc
{
DWORD lpNumberOfBytes;
ULONG_PTR key;
LPOVERLAPPED overlap;
size_t key;
OVERLAPPED* overlap;
immutable timeout = cast(immutable int) blockTime.total!"msecs";
auto result = GetQueuedCompletionStatus(completionPort,
@ -196,7 +293,7 @@ class IOCPLoop : Loop
&key,
&overlap,
timeout);
if (result == FALSE && overlap == NULL)
if (result == FALSE && overlap is null)
{
return; // Timeout
}
@ -218,29 +315,26 @@ class IOCPLoop : Loop
assert(listener !is null);
auto socket = listener.endAccept(overlapped);
auto transport = MmapPool.instance.make!IOCPStreamTransport(socket);
auto io = MmapPool.instance.make!IOWatcher(transport, connection.protocol);
auto transport = MmapPool.instance.make!StreamTransport(socket);
connection.incoming.insertBack(io);
connection.incoming.enqueue(transport);
reify(io, EventMask(Event.none), EventMask(Event.read, Event.write));
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
swapPendings.insertBack(connection);
pendings.enqueue(connection);
listener.beginAccept(overlapped);
break;
case OverlappedSocketEvent.read:
auto io = cast(IOWatcher) (cast(void*) key);
assert(io !is null);
if (!io.active)
auto transport = cast(StreamTransport) (cast(void*) key);
assert(transport !is null);
if (!transport.active)
{
MmapPool.instance.dispose(io);
MmapPool.instance.dispose(transport);
MmapPool.instance.dispose(overlapped);
return;
}
auto transport = cast(IOCPStreamTransport) io.transport;
assert(transport !is null);
int received;
SocketException exception;
try
@ -253,38 +347,39 @@ class IOCPLoop : Loop
}
if (transport.socket.disconnected)
{
// We want to get one last notification to destroy the watcher
transport.socket.beginReceive(io.output[], overlapped);
kill(io, exception);
// We want to get one last notification to destroy the watcher.
transport.socket.beginReceive(transport.output[], overlapped);
kill(transport, exception);
}
else if (received > 0)
{
immutable full = io.output.free == received;
immutable full = transport.output.free == received;
io.output += received;
// Receive was interrupted because the buffer is full. We have to continue
transport.output += received;
// Receive was interrupted because the buffer is full. We have to continue.
if (full)
{
transport.socket.beginReceive(io.output[], overlapped);
transport.socket.beginReceive(transport.output[], overlapped);
}
swapPendings.insertBack(io);
pendings.enqueue(transport);
}
break;
case OverlappedSocketEvent.write:
auto io = cast(IOWatcher) (cast(void*) key);
assert(io !is null);
auto transport = cast(IOCPStreamTransport) io.transport;
auto transport = cast(StreamTransport) (cast(void*) key);
assert(transport !is null);
transport.input += transport.socket.endSend(overlapped);
if (transport.input.length)
if (transport.input.length > 0)
{
transport.socket.beginSend(transport.input[], overlapped);
}
else
{
transport.socket.beginReceive(io.output[], overlapped);
transport.socket.beginReceive(transport.output[], overlapped);
if (transport.isClosing())
{
kill(transport);
}
}
break;
default:

View File

@ -2,47 +2,70 @@
* 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.
/*
* Event loop implementation for *BSD.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto: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/kqueue.d,
* tanya/async/event/kqueue.d)
*/
module tanya.async.event.kqueue;
version (OSX)
version (D_Ddoc)
{
version = MissingKevent;
}
else version (OSX)
{
version = MacBSD;
}
else version (iOS)
{
version = MissingKevent;
version = MacBSD;
}
else version (TVOS)
{
version = MissingKevent;
version = MacBSD;
}
else version (WatchOS)
{
version = MissingKevent;
version = MacBSD;
}
else version (FreeBSD)
{
version = MacBSD;
}
else version (OpenBSD)
{
version = MissingKevent;
version = MacBSD;
}
else version (DragonFlyBSD)
{
version = MissingKevent;
version = MacBSD;
}
version (MissingKevent)
{
extern (C):
nothrow:
@nogc:
version (MacBSD):
import core.stdc.stdint; // intptr_t, uintptr_t
import core.stdc.errno;
import core.sys.posix.time; // timespec
import core.sys.posix.unistd;
import core.time;
import std.algorithm.comparison;
import tanya.async.event.selector;
import tanya.async.loop;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.array;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc
{
*kevp = kevent_t(args);
}
enum : short
{
@ -60,11 +83,6 @@ version (MissingKevent)
EVFILT_SYSCOUNT = 11
}
extern(D) void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args)
{
*kevp = kevent_t(args);
}
struct kevent_t
{
uintptr_t ident; /* identifier for this event */
@ -97,92 +115,54 @@ version (MissingKevent)
EV_ERROR = 0x4000, /* error, data contains errno */
}
int kqueue();
int kevent(int kq, const kevent_t *changelist, int nchanges,
kevent_t *eventlist, int nevents,
const timespec *timeout);
}
extern(C) int kqueue() nothrow @nogc;
extern(C) int kevent(int kq, const kevent_t *changelist, int nchanges,
kevent_t *eventlist, int nevents, const timespec *timeout)
nothrow @nogc;
version (OSX)
{
version = MacBSD;
}
else version (iOS)
{
version = MacBSD;
}
else version (FreeBSD)
{
version = MacBSD;
public import core.sys.freebsd.sys.event;
}
else version (OpenBSD)
{
version = MacBSD;
}
else version (DragonFlyBSD)
{
version = MacBSD;
}
version (MacBSD):
import dlib.async.event.selector;
import dlib.async.loop;
import dlib.async.transport;
import dlib.async.watcher;
import dlib.memory;
import dlib.memory.mmappool;
import dlib.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
final class KqueueLoop : SelectorLoop
{
protected int fd;
private kevent_t[] events;
private kevent_t[] changes;
private Array!kevent_t events;
private Array!kevent_t changes;
private size_t changeCount;
/**
* Returns: Maximal event count can be got at a time
* (should be supported by the backend).
*/
override protected @property inout(uint) maxEvents() inout const pure nothrow @safe @nogc
override protected @property uint maxEvents()
const pure nothrow @safe @nogc
{
return cast(uint) events.length;
}
this()
this() @nogc
{
super();
if ((fd = kqueue()) == -1)
{
throw MmapPool.instance.make!BadLoopException("epoll initialization failed");
throw make!BadLoopException(defaultAllocator,
"kqueue initialization failed");
}
events = MmapPool.instance.makeArray!kevent_t(64);
changes = MmapPool.instance.makeArray!kevent_t(64);
events = Array!kevent_t(64, MmapPool.instance);
changes = Array!kevent_t(64, MmapPool.instance);
}
/**
* Free loop internals.
* Frees loop internals.
*/
~this()
~this() @nogc
{
MmapPool.instance.dispose(events);
MmapPool.instance.dispose(changes);
close(fd);
}
private void set(socket_t socket, short filter, ushort flags)
private void set(SocketType socket, short filter, ushort flags) @nogc
{
if (changes.length <= changeCount)
{
MmapPool.instance.resizeArray(changes, changeCount + maxEvents);
changes.length = changeCount + maxEvents;
}
EV_SET(&changes[changeCount],
cast(ulong) socket,
@ -204,9 +184,9 @@ class KqueueLoop : SelectorLoop
*
* Returns: $(D_KEYWORD true) if the operation was successful.
*/
override protected bool reify(ConnectionWatcher watcher,
override protected bool reify(SocketWatcher watcher,
EventMask oldEvents,
EventMask events)
EventMask events) @nogc
{
if (events != oldEvents)
{
@ -233,17 +213,22 @@ class KqueueLoop : SelectorLoop
/**
* Does the actual polling.
*/
protected override void poll()
protected override void poll() @nogc
{
timespec ts;
blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec);
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,
changes.get().ptr,
cast(int) changeCount,
events.get().ptr,
maxEvents,
&ts);
changeCount = 0;
if (eventCount < 0)
@ -259,29 +244,29 @@ class KqueueLoop : SelectorLoop
{
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 (io is null)
if (transport is null)
{
acceptConnections(connections[events[i].ident]);
auto connection = cast(ConnectionWatcher) connections[events[i].ident];
assert(connection !is null);
acceptConnections(connection);
}
else if (events[i].flags & EV_ERROR)
{
kill(io, null);
kill(transport);
}
else if (events[i].filter == EVFILT_READ)
{
auto transport = cast(SelectorStreamTransport) io.transport;
assert(transport !is null);
SocketException exception;
try
{
ptrdiff_t received;
do
{
received = transport.socket.receive(io.output[]);
io.output += received;
received = transport.socket.receive(transport.output[]);
transport.output += received;
}
while (received);
}
@ -291,18 +276,15 @@ class KqueueLoop : SelectorLoop
}
if (transport.socket.disconnected)
{
kill(io, exception);
kill(transport, exception);
}
else if (io.output.length)
else if (transport.output.length)
{
swapPendings.insertBack(io);
pendings.enqueue(transport);
}
}
else if (events[i].filter == EVFILT_WRITE)
{
auto transport = cast(SelectorStreamTransport) io.transport;
assert(transport !is null);
transport.writeReady = true;
if (transport.input.length)
{
@ -316,7 +298,7 @@ class KqueueLoop : SelectorLoop
* Returns: The blocking time.
*/
override protected @property inout(Duration) blockTime()
inout @safe pure nothrow
inout @nogc @safe pure nothrow
{
return min(super.blockTime, 1.dur!"seconds");
}
@ -331,9 +313,10 @@ class KqueueLoop : SelectorLoop
*
* Returns: $(D_KEYWORD true) if the operation could be successfully
* completed or scheduled, $(D_KEYWORD false) otherwise (the
* transport is be destroyed then).
* transport will be destroyed then).
*/
protected override bool feed(SelectorStreamTransport transport, SocketException exception = null)
protected override bool feed(StreamTransport transport,
SocketException exception = null) @nogc
{
if (!super.feed(transport, exception))
{

View File

@ -2,38 +2,50 @@
* 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.
/*
* This module contains base implementations for reactor event loops.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto: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;
version (Posix):
version (D_Ddoc)
{
}
else version (Posix):
import tanya.async.loop;
import tanya.async.protocol;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.array;
import tanya.container.buffer;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
import core.sys.posix.netinet.in_;
import core.stdc.errno;
/**
* Transport for stream sockets.
*/
class SelectorStreamTransport : StreamTransport
package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
{
private ConnectedSocket socket_;
/// Input buffer.
package WriteBuffer input;
private SelectorLoop loop;
private SocketException exception;
package ReadBuffer!ubyte output;
package WriteBuffer!ubyte input;
private Protocol protocol_;
private bool closing;
/// Received notification that the underlying socket is write-ready.
package bool writeReady;
@ -41,28 +53,120 @@ class SelectorStreamTransport : StreamTransport
* Params:
* loop = Event loop.
* socket = Socket.
*
* Precondition: $(D_INLINECODE loop !is null && socket !is null)
*/
this(SelectorLoop loop, ConnectedSocket socket)
this(SelectorLoop loop, ConnectedSocket socket) @nogc
in
{
assert(loop !is null);
}
body
{
super(socket);
this.loop = loop;
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
input = WriteBuffer!ubyte(8192, MmapPool.instance);
active = true;
}
/**
* Returns: Socket.
*
* Postcondition: $(D_INLINECODE socket !is null)
*/
override @property ConnectedSocket socket() pure nothrow @safe @nogc
out (socket)
{
assert(socket !is null);
}
body
{
return cast(ConnectedSocket) socket_;
}
private @property void socket(ConnectedSocket socket)
pure nothrow @safe @nogc
in
{
assert(socket !is null);
}
body
{
socket_ = socket;
this.loop = loop;
input = MmapPool.instance.make!WriteBuffer();
}
/**
* Close the transport and deallocate the data buffers.
* Returns: Application protocol.
*/
~this()
@property Protocol protocol() pure nothrow @safe @nogc
{
MmapPool.instance.dispose(input);
return protocol_;
}
/**
* Returns: Transport socket.
* Switches the protocol.
*
* The protocol is deallocated by the event loop, it should currently be
* allocated with $(D_PSYMBOL MmapPool).
*
* Params:
* protocol = Application protocol.
*
* Precondition: $(D_INLINECODE protocol !is null)
*/
inout(ConnectedSocket) socket() inout pure nothrow @safe @nogc
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
in
{
return socket_;
assert(protocol !is null);
}
body
{
protocol_ = protocol;
}
/**
* Returns $(D_PARAM true) if the transport is closing or closed.
*/
bool isClosing() const pure nothrow @safe @nogc
{
return closing;
}
/**
* Close the transport.
*
* Buffered data will be flushed. No more data will be received.
*/
void close() @nogc
{
closing = true;
loop.reify(this,
EventMask(Event.read, Event.write),
EventMask(Event.write));
}
/**
* Invokes the watcher callback.
*/
override void invoke() @nogc
{
if (output.length)
{
protocol.received(output[0 .. $]);
output.clear();
if (isClosing() && input.length == 0)
{
loop.kill(this);
}
}
else
{
protocol.disconnected(exception);
MmapPool.instance.dispose(protocol_);
defaultAllocator.dispose(exception);
active = false;
}
}
/**
@ -71,7 +175,7 @@ class SelectorStreamTransport : StreamTransport
* Params:
* data = Data to send.
*/
void write(ubyte[] data)
void write(ubyte[] data) @nogc
{
if (!data.length)
{
@ -111,28 +215,60 @@ class SelectorStreamTransport : StreamTransport
abstract class SelectorLoop : Loop
{
/// Pending connections.
protected ConnectionWatcher[] connections;
protected Array!SocketWatcher connections;
this()
this() @nogc
{
super();
connections = MmapPool.instance.makeArray!ConnectionWatcher(maxEvents);
connections = Array!SocketWatcher(maxEvents, MmapPool.instance);
}
~this()
~this() @nogc
{
foreach (ref connection; connections)
{
// We want to free only IOWatchers. ConnectionWatcher are created by the
// user and should be freed by himself.
auto io = cast(IOWatcher) connection;
if (io !is null)
// We want to free only the transports. ConnectionWatcher are
// created by the user and should be freed by himself.
if (cast(StreamTransport) connection !is null)
{
MmapPool.instance.dispose(io);
connection = null;
MmapPool.instance.dispose(connection);
}
}
MmapPool.instance.dispose(connections);
}
/**
* Should be called if the backend configuration changes.
*
* Params:
* watcher = Watcher.
* oldEvents = The events were already set.
* events = The events should be set.
*
* Returns: $(D_KEYWORD true) if the operation was successful.
*/
override abstract protected bool reify(SocketWatcher watcher,
EventMask oldEvents,
EventMask events) @nogc;
/**
* Kills the watcher and closes the connection.
*
* Params:
* transport = Transport.
* exception = Occurred exception.
*/
protected void kill(StreamTransport transport,
SocketException exception = null) @nogc
in
{
assert(transport !is null);
}
body
{
transport.socket.shutdown();
defaultAllocator.dispose(transport.socket);
transport.exception = exception;
pendings.enqueue(transport);
}
/**
@ -147,7 +283,13 @@ abstract class SelectorLoop : Loop
* completed or scheduled, $(D_KEYWORD false) otherwise (the
* transport will be destroyed then).
*/
protected bool feed(SelectorStreamTransport transport, SocketException exception = null)
protected bool feed(StreamTransport transport,
SocketException exception = null) @nogc
in
{
assert(transport !is null);
}
body
{
while (transport.input.length && transport.writeReady)
{
@ -171,12 +313,13 @@ abstract class SelectorLoop : Loop
}
if (exception !is null)
{
auto watcher = cast(IOWatcher) connections[transport.socket.handle];
assert(watcher !is null);
kill(watcher, exception);
kill(transport, exception);
return false;
}
if (transport.input.length == 0 && transport.isClosing())
{
kill(transport);
}
return true;
}
@ -186,7 +329,7 @@ abstract class SelectorLoop : Loop
* Params:
* watcher = Watcher.
*/
override void start(ConnectionWatcher watcher)
override void start(ConnectionWatcher watcher) @nogc
{
if (watcher.active)
{
@ -195,7 +338,7 @@ abstract class SelectorLoop : Loop
if (connections.length <= watcher.socket)
{
MmapPool.instance.resizeArray(connections, watcher.socket.handle + maxEvents / 2);
connections.length = watcher.socket.handle + maxEvents / 2;
}
connections[watcher.socket.handle] = watcher;
@ -208,7 +351,7 @@ abstract class SelectorLoop : Loop
* Params:
* connection = Connection watcher ready to accept.
*/
package void acceptConnections(ConnectionWatcher connection)
package void acceptConnections(ConnectionWatcher connection) @nogc
in
{
assert(connection !is null);
@ -232,35 +375,33 @@ abstract class SelectorLoop : Loop
break;
}
IOWatcher io;
auto transport = MmapPool.instance.make!SelectorStreamTransport(this, client);
StreamTransport transport;
if (connections.length >= client.handle)
if (connections.length > client.handle)
{
io = cast(IOWatcher) connections[client.handle];
transport = cast(StreamTransport) connections[client.handle];
}
else
{
MmapPool.instance.resizeArray(connections, client.handle + maxEvents / 2);
connections.length = client.handle + maxEvents / 2;
}
if (io is null)
if (transport is null)
{
io = MmapPool.instance.make!IOWatcher(transport,
connection.protocol);
connections[client.handle] = io;
transport = MmapPool.instance.make!StreamTransport(this, client);
connections[client.handle] = transport;
}
else
{
io(transport, connection.protocol);
transport.socket = client;
}
reify(io, EventMask(Event.none), EventMask(Event.read, Event.write));
connection.incoming.insertBack(io);
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
connection.incoming.enqueue(transport);
}
if (!connection.incoming.empty)
{
swapPendings.insertBack(connection);
pendings.enqueue(connection);
}
}
}

View File

@ -3,17 +3,41 @@
* 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-2017.
* 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)
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/iocp.d,
* tanya/async/iocp.d)
*/
module tanya.async.iocp;
version (Windows):
version (Windows)
{
version = WindowsDoc;
}
else version (D_Ddoc)
{
version = WindowsDoc;
version (Windows)
{
}
else
{
private struct OVERLAPPED
{
}
private alias HANDLE = void*;
}
}
import core.sys.windows.winbase;
import core.sys.windows.windef;
version (WindowsDoc):
import tanya.sys.windows.winbase;
/**
* Provides an extendable representation of a Win32 $(D_PSYMBOL OVERLAPPED)

View File

@ -3,77 +3,90 @@
* 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)
* Interface for the event loop implementations and the default event loop
* chooser.
*
* ---
* import tanya.async;
* import tanya.memory;
* import tanya.network.socket;
*
* class EchoProtocol : TransmissionControlProtocol
* {
* private DuplexTransport transport;
*
* void received(ubyte[] data)
* void received(in ubyte[] data) @nogc
* {
* transport.write(data);
* }
*
* void connected(DuplexTransport transport)
* void connected(DuplexTransport transport) @nogc
* {
* this.transport = transport;
* }
*
* void disconnected(SocketException exception = null)
* void disconnected(SocketException e) @nogc
* {
* }
* }
*
* void main()
* {
* auto address = new InternetAddress("127.0.0.1", cast(ushort) 8192);
* auto address = defaultAllocator.make!InternetAddress("127.0.0.1", cast(ushort) 8192);
*
* version (Windows)
* {
* auto sock = new OverlappedStreamSocket(AddressFamily.INET);
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.inet);
* }
* else
* {
* auto sock = new StreamSocket(AddressFamily.INET);
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.inet);
* sock.blocking = false;
* }
*
* sock.bind(address);
* sock.listen(5);
*
* auto io = new ConnectionWatcher(sock);
* auto io = defaultAllocator.make!ConnectionWatcher(sock);
* io.setProtocol!EchoProtocol;
*
* defaultLoop.start(io);
* defaultLoop.run();
*
* sock.shutdown();
* defaultAllocator.dispose(io);
* defaultAllocator.dispose(sock);
* defaultAllocator.dispose(address);
* }
* ---
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/loop.d,
* tanya/async/loop.d)
*/
module tanya.async.loop;
import tanya.async.protocol;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.buffer;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
import core.time;
import std.algorithm.iteration;
import std.algorithm.mutation;
import std.typecons;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.buffer;
import tanya.container.queue;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
version (DisableBackends)
{
}
else version (D_Ddoc)
{
}
else version (linux)
{
import tanya.async.event.epoll;
@ -104,6 +117,30 @@ else version (DragonFlyBSD)
{
version = Kqueue;
}
version (unittest)
{
final class TestLoop : Loop
{
override protected bool reify(SocketWatcher watcher,
EventMask oldEvents,
EventMask events) @nogc
{
return true;
}
override protected void poll() @nogc
{
assert(!this.done);
unloop();
}
override protected @property uint maxEvents()
const pure nothrow @safe @nogc
{
return 64U;
}
}
}
/**
* Events.
@ -119,78 +156,103 @@ enum Event : uint
alias EventMask = BitFlags!Event;
/**
* Tries to set $(D_PSYMBOL MmapPool) to the default allocator.
*/
shared static this()
{
if (allocator is null)
{
allocator = MmapPool.instance;
}
}
/**
* Event loop.
*/
abstract class Loop
{
/// Pending watchers.
protected PendingQueue!Watcher pendings;
private bool done = true;
protected PendingQueue!Watcher swapPendings;
/// Pending watchers.
protected Queue!Watcher pendings;
/**
* Returns: Maximal event count can be got at a time
* (should be supported by the backend).
*/
protected @property inout(uint) maxEvents() inout const pure nothrow @safe @nogc
protected @property uint maxEvents()
const pure nothrow @safe @nogc
{
return 128U;
}
private unittest
{
auto loop = defaultAllocator.make!TestLoop;
assert(loop.maxEvents == 64);
defaultAllocator.dispose(loop);
}
/**
* Initializes the loop.
*/
this()
this() @nogc
{
pendings = MmapPool.instance.make!(PendingQueue!Watcher);
swapPendings = MmapPool.instance.make!(PendingQueue!Watcher);
pendings = Queue!Watcher(MmapPool.instance);
}
/**
* Frees loop internals.
*/
~this()
~this() @nogc
{
MmapPool.instance.dispose(pendings);
MmapPool.instance.dispose(swapPendings);
foreach (w; pendings)
{
MmapPool.instance.dispose(w);
}
}
/**
* Starts the loop.
*/
void run()
void run() @nogc
{
done_ = false;
this.done = false;
do
{
poll();
// Invoke pendings
swapPendings.each!((ref p) => p.invoke());
swap(pendings, swapPendings);
foreach (ref w; this.pendings)
{
w.invoke();
}
while (!done_);
}
while (!this.done);
}
/**
* Break out of the loop.
*/
void unloop() @safe pure nothrow
void unloop() @safe pure nothrow @nogc
{
done_ = true;
this.done = true;
}
private unittest
{
auto loop = defaultAllocator.make!TestLoop;
assert(loop.done);
loop.run();
assert(loop.done);
defaultAllocator.dispose(loop);
}
private unittest
{
auto loop = defaultAllocator.make!TestLoop;
auto watcher = defaultAllocator.make!DummyWatcher;
loop.pendings.enqueue(watcher);
assert(!watcher.invoked);
loop.run();
assert(watcher.invoked);
defaultAllocator.dispose(loop);
defaultAllocator.dispose(watcher);
}
/**
@ -199,13 +261,14 @@ abstract class Loop
* Params:
* watcher = Watcher.
*/
void start(ConnectionWatcher watcher)
void start(ConnectionWatcher watcher) @nogc
{
if (watcher.active)
{
return;
}
watcher.active = true;
reify(watcher, EventMask(Event.none), EventMask(Event.accept));
}
@ -215,7 +278,7 @@ abstract class Loop
* Params:
* watcher = Watcher.
*/
void stop(ConnectionWatcher watcher)
void stop(ConnectionWatcher watcher) @nogc
{
if (!watcher.active)
{
@ -236,18 +299,18 @@ abstract class Loop
*
* Returns: $(D_KEYWORD true) if the operation was successful.
*/
abstract protected bool reify(ConnectionWatcher watcher,
abstract protected bool reify(SocketWatcher watcher,
EventMask oldEvents,
EventMask events);
EventMask events) @nogc;
/**
* Returns: The blocking time.
*/
protected @property inout(Duration) blockTime()
inout @safe pure nothrow
inout @safe pure nothrow @nogc
{
// Don't block if we have to do.
return swapPendings.empty ? blockTime_ : Duration.zero;
return pendings.empty ? blockTime_ : Duration.zero;
}
/**
@ -257,7 +320,7 @@ abstract class Loop
* blockTime = The blocking time. Cannot be larger than
* $(D_PSYMBOL maxBlockTime).
*/
protected @property void blockTime(in Duration blockTime) @safe pure nothrow
protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc
in
{
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
@ -268,25 +331,21 @@ abstract class Loop
blockTime_ = blockTime;
}
/**
* Kills the watcher and closes the connection.
*/
protected void kill(IOWatcher watcher, SocketException exception)
private unittest
{
watcher.socket.shutdown();
defaultAllocator.dispose(watcher.socket);
MmapPool.instance.dispose(watcher.transport);
watcher.exception = exception;
swapPendings.insertBack(watcher);
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();
/// Whether the event loop should be stopped.
private bool done_;
abstract protected void poll() @nogc;
/// Maximal block time.
protected Duration blockTime_ = 1.dur!"minutes";
@ -297,7 +356,6 @@ abstract class Loop
*/
class BadLoopException : Exception
{
@nogc:
/**
* Params:
* file = The file where the exception occurred.
@ -305,7 +363,7 @@ class BadLoopException : Exception
* next = The previous exception in the chain of exceptions, if any.
*/
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
pure @safe nothrow const
pure nothrow const @safe @nogc
{
super("Event loop cannot be initialized.", file, line, next);
}
@ -318,7 +376,7 @@ class BadLoopException : Exception
*
* Returns: The default event loop.
*/
@property Loop defaultLoop()
@property Loop defaultLoop() @nogc
{
if (defaultLoop_ !is null)
{
@ -351,7 +409,7 @@ class BadLoopException : Exception
* Params:
* loop = The event loop.
*/
@property void defaultLoop(Loop loop)
@property void defaultLoop(Loop loop) @nogc
in
{
assert(loop !is null);
@ -363,131 +421,15 @@ body
private Loop defaultLoop_;
/**
* Queue.
*
* Params:
* T = Content type.
*/
class PendingQueue(T)
{
/**
* Creates a new $(D_PSYMBOL Queue).
*/
this()
private unittest
{
}
/**
* Removes all elements from the queue.
*/
~this()
{
foreach (e; this)
{
MmapPool.instance.dispose(e);
}
}
/**
* Returns: First element.
*/
@property ref T front()
in
{
assert(!empty);
}
body
{
return first.next.content;
}
/**
* Inserts a new element.
*
* Params:
* x = New element.
*
* Returns: $(D_KEYWORD this).
*/
typeof(this) insertBack(T x)
{
Entry* temp = MmapPool.instance.make!Entry;
temp.content = x;
if (empty)
{
first.next = rear = temp;
}
else
{
rear.next = temp;
rear = rear.next;
}
return this;
}
alias insert = insertBack;
/**
* Inserts a new element.
*
* Params:
* x = New element.
*
* Returns: $(D_KEYWORD this).
*/
typeof(this) opOpAssign(string Op)(ref T x)
if (Op == "~")
{
return insertBack(x);
}
/**
* Returns: $(D_KEYWORD true) if the queue is empty.
*/
@property bool empty() const @safe pure nothrow
{
return first.next is null;
}
/**
* Move position to the next element.
*
* Returns: $(D_KEYWORD this).
*/
typeof(this) popFront()
in
{
assert(!empty);
}
body
{
auto n = first.next.next;
MmapPool.instance.dispose(first.next);
first.next = n;
return this;
}
/**
* Queue entry.
*/
protected struct Entry
{
/// Queue item content.
T content;
/// Next list item.
Entry* next;
}
/// The first element of the list.
protected Entry first;
/// The last element of the list.
protected Entry* rear;
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,17 +3,18 @@
* 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-2017.
* 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)
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/package.d,
* tanya/async/package.d)
*/
module tanya.async;
public
{
import tanya.async.loop;
import tanya.async.protocol;
import tanya.async.transport;
import tanya.async.watcher;
}
public import tanya.async.loop;
public import tanya.async.protocol;
public import tanya.async.transport;
public import tanya.async.watcher;

View File

@ -3,15 +3,23 @@
* 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-2017.
* 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)
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/protocol.d,
* tanya/async/protocol.d)
*/
module tanya.async.protocol;
import tanya.network.socket;
import tanya.async.transport;
import tanya.network.socket;
/**
* Common protocol interface.
@ -22,7 +30,7 @@ interface Protocol
* Params:
* data = Read data.
*/
void received(ubyte[] data);
void received(in ubyte[] data) @nogc;
/**
* Called when a connection is made.
@ -30,7 +38,7 @@ interface Protocol
* Params:
* transport = Protocol transport.
*/
void connected(DuplexTransport transport);
void connected(DuplexTransport transport) @nogc;
/**
* Called when a connection is lost.
@ -39,7 +47,7 @@ interface Protocol
* exception = $(D_PSYMBOL Exception) if an error caused
* the disconnect, $(D_KEYWORD null) otherwise.
*/
void disconnected(SocketException exception = null);
void disconnected(SocketException exception) @nogc;
}
/**

View File

@ -3,13 +3,19 @@
* 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-2017.
* 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)
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/transport.d,
* tanya/async/transport.d)
*/
module tanya.async.transport;
import tanya.async.protocol;
import tanya.network.socket;
/**
@ -37,7 +43,7 @@ interface WriteTransport : Transport
* Params:
* data = Data to send.
*/
void write(ubyte[] data);
void write(ubyte[] data) @nogc;
}
/**
@ -45,6 +51,46 @@ interface WriteTransport : Transport
*/
interface DuplexTransport : ReadTransport, WriteTransport
{
/**
* Returns: Application protocol.
*
* Postcondition: $(D_INLINECODE protocol !is null)
*/
@property Protocol protocol() pure nothrow @safe @nogc
out (protocol)
{
assert(protocol !is null);
}
/**
* Switches the protocol.
*
* The protocol is deallocated by the event loop, it should currently be
* allocated with $(D_PSYMBOL MmapPool).
*
* Params:
* protocol = Application protocol.
*
* Precondition: $(D_INLINECODE protocol !is null)
*/
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
in
{
assert(protocol !is null);
}
/**
* Returns $(D_PARAM true) if the transport is closing or closed.
*/
bool isClosing() const pure nothrow @safe @nogc;
/**
* Close the transport.
*
* Buffered data will be flushed. No more data will be received.
*/
void close() @nogc;
}
/**
@ -52,12 +98,8 @@ interface DuplexTransport : ReadTransport, WriteTransport
*/
interface SocketTransport : Transport
{
@property inout(Socket) socket() inout pure nothrow @safe @nogc;
}
/**
* Represents a connection-oriented socket transport.
* Returns: Socket.
*/
package interface StreamTransport : DuplexTransport, SocketTransport
{
@property Socket socket() pure nothrow @safe @nogc;
}

View File

@ -3,31 +3,27 @@
* 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-2017.
* 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)
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/watcher.d,
* tanya/async/watcher.d)
*/
module tanya.async.watcher;
import std.exception;
import std.functional;
import tanya.async.loop;
import tanya.async.protocol;
import tanya.async.transport;
import tanya.container.buffer;
import tanya.container.queue;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
import std.functional;
import std.exception;
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
@ -41,202 +37,98 @@ abstract class Watcher
/**
* Invoke some action on event.
*/
void invoke();
void invoke() @nogc;
}
class ConnectionWatcher : Watcher
version (unittest)
{
final class DummyWatcher : Watcher
{
bool invoked;
override void invoke() @nogc
{
this.invoked = true;
}
}
}
/**
* Socket watcher.
*/
abstract class SocketWatcher : Watcher
{
/// Watched socket.
private Socket socket_;
/// Protocol factory.
protected Protocol delegate() protocolFactory;
package PendingQueue!IOWatcher incoming;
protected Socket socket_;
/**
* Params:
* socket = Socket.
*
* Precondition: $(D_INLINECODE socket !is null)
*/
this(Socket socket)
this(Socket socket) pure nothrow @safe @nogc
in
{
assert(socket !is null);
}
body
{
socket_ = socket;
incoming = MmapPool.instance.make!(PendingQueue!IOWatcher);
}
/// Ditto.
protected this()
{
}
~this()
{
MmapPool.instance.dispose(incoming);
}
/*
* Params:
* P = Protocol should be used.
*/
void setProtocol(P : Protocol)()
{
this.protocolFactory = () => cast(Protocol) MmapPool.instance.make!P;
}
/**
* Returns: Socket.
*/
@property inout(Socket) socket() inout pure nothrow @nogc
@property Socket socket() pure nothrow @safe @nogc
{
return socket_;
}
}
/**
* Returns: New protocol instance.
* Connection watcher.
*/
@property Protocol protocol()
class ConnectionWatcher : SocketWatcher
{
/// Incoming connection queue.
Queue!DuplexTransport incoming;
private Protocol delegate() @nogc protocolFactory;
/**
* Params:
* socket = Socket.
*/
this(Socket socket) @nogc
{
super(socket);
incoming = Queue!DuplexTransport(MmapPool.instance);
}
/**
* Params:
* P = Protocol should be used.
*/
void setProtocol(P : Protocol)() @nogc
{
this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P;
}
/**
* Invokes new connection callback.
*/
override void invoke() @nogc
in
{
assert(protocolFactory !is null, "Protocol isn't set.");
}
body
{
return protocolFactory();
}
/**
* Invokes new connection callback.
*/
override void invoke()
foreach (transport; incoming)
{
foreach (io; incoming)
{
io.protocol.connected(cast(DuplexTransport) io.transport);
}
}
}
/**
* Contains a pending watcher with the invoked events or a transport can be
* read from.
*/
class IOWatcher : ConnectionWatcher
{
/// If an exception was thrown the transport should be already invalid.
private union
{
StreamTransport transport_;
SocketException exception_;
}
private Protocol protocol_;
/**
* Returns: Underlying output buffer.
*/
package ReadBuffer output;
/**
* Params:
* transport = Transport.
* protocol = New instance of the application protocol.
*/
this(StreamTransport transport, Protocol protocol)
in
{
assert(transport !is null);
assert(protocol !is null);
}
body
{
super();
transport_ = transport;
protocol_ = protocol;
output = MmapPool.instance.make!ReadBuffer();
active = true;
}
/**
* Destroys the watcher.
*/
protected ~this()
{
MmapPool.instance.dispose(output);
MmapPool.instance.dispose(protocol_);
}
/**
* Assigns a transport.
*
* Params:
* transport = Transport.
* protocol = Application protocol.
*
* Returns: $(D_KEYWORD this).
*/
IOWatcher opCall(StreamTransport transport, Protocol protocol) pure nothrow @safe @nogc
in
{
assert(transport !is null);
assert(protocol !is null);
}
body
{
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()
{
if (output.length)
{
protocol.received(output[0..$]);
output.clear();
}
else
{
protocol.disconnected(exception_);
active = false;
transport.protocol = protocolFactory();
transport.protocol.connected(transport);
}
}
}

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.container.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

@ -3,14 +3,19 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Copyright: Eugene Wissner 2016.
* This module contains buffers designed for C-style input/output APIs.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto: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/container/buffer.d,
* tanya/container/buffer.d)
*/
module tanya.container.buffer;
import tanya.memory;
import tanya.meta.trait;
version (unittest)
{
@ -33,51 +38,6 @@ version (unittest)
}
}
/**
* Interface for implemeting input/output buffers.
*/
interface Buffer
{
/**
* Returns: The size of the internal buffer.
*/
@property size_t capacity() const @nogc @safe pure nothrow;
/**
* Returns: Data size.
*/
@property size_t length() const @nogc @safe pure nothrow;
/**
* Returns: Available space.
*/
@property size_t free() const @nogc @safe pure nothrow;
/**
* Params:
* start = Start position.
* end = End position.
*
* Returns: Array between $(D_PARAM start) and $(D_PARAM end).
*/
@property ubyte[] opSlice(size_t start, size_t end)
in
{
assert(start <= end);
assert(end <= length);
}
/**
* Returns: Length of available data.
*/
@property size_t opDollar() const pure nothrow @safe @nogc;
/**
* Returns: Data chunk.
*/
@property ubyte[] opIndex();
}
/**
* Self-expanding buffer, that can be used with functions returning the number
* of the read bytes.
@ -87,26 +47,30 @@ interface Buffer
* available data. But only one asynchronous call at a time is supported. Be
* sure to call $(D_PSYMBOL ReadBuffer.clear()) before you append the result
* of the pended asynchronous call.
*
* Params:
* T = Buffer type.
*/
class ReadBuffer : Buffer
struct ReadBuffer(T = ubyte)
if (isScalarType!T)
{
/// Internal buffer.
protected ubyte[] buffer_;
private T[] buffer_;
/// Filled buffer length.
protected size_t length_;
private size_t length_;
/// Start of available data.
protected size_t start;
private size_t start;
/// Last position returned with $(D_KEYWORD []).
protected size_t ring;
private size_t ring;
/// Available space.
protected immutable size_t minAvailable;
private immutable size_t minAvailable = 1024;
/// Size by which the buffer will grow.
protected immutable size_t blockSize;
private immutable size_t blockSize = 8192;
invariant
{
@ -123,39 +87,50 @@ class ReadBuffer : Buffer
* will grow.
* minAvailable = minimal size should be always available to fill.
* So it will reallocate if $(D_INLINECODE
* $(D_PSYMBOL free) < $(D_PARAM minAvailable)
* ).
* $(D_PSYMBOL free) < $(D_PARAM minAvailable)).
* allocator = Allocator.
*/
this(size_t size = 8192,
size_t minAvailable = 1024)
this(in size_t size,
in size_t minAvailable = 1024,
shared Allocator allocator = defaultAllocator) @trusted
{
this(allocator);
this.minAvailable = minAvailable;
this.blockSize = size;
defaultAllocator.resizeArray!ubyte(buffer_, size);
buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
}
/// ditto
this(shared Allocator allocator)
in
{
assert(allocator_ is null);
}
body
{
allocator_ = allocator;
}
/**
* Deallocates the internal buffer.
*/
~this()
~this() @trusted
{
defaultAllocator.dispose(buffer_);
allocator.deallocate(buffer_);
}
///
unittest
{
auto b = defaultAllocator.make!ReadBuffer;
assert(b.capacity == 8192);
ReadBuffer!ubyte b;
assert(b.capacity == 0);
assert(b.length == 0);
defaultAllocator.dispose(b);
}
/**
* Returns: The size of the internal buffer.
*/
@property size_t capacity() const @nogc @safe pure nothrow
@property size_t capacity() const
{
return buffer_.length;
}
@ -163,26 +138,28 @@ class ReadBuffer : Buffer
/**
* Returns: Data size.
*/
@property size_t length() const @nogc @safe pure nothrow
@property size_t length() const
{
return length_ - start;
}
/// ditto
alias opDollar = length;
/**
* Clears the buffer.
*
* Returns: $(D_KEYWORD this).
*/
ReadBuffer clear() pure nothrow @safe @nogc
void clear()
{
start = length_ = ring;
return this;
}
/**
* Returns: Available space.
*/
@property size_t free() const pure nothrow @safe @nogc
@property size_t free() const
{
return length > ring ? capacity - length : capacity - ring;
}
@ -190,19 +167,17 @@ class ReadBuffer : Buffer
///
unittest
{
auto b = defaultAllocator.make!ReadBuffer;
ReadBuffer!ubyte b;
size_t numberRead;
// Fills the buffer with values 0..10
assert(b.free == b.blockSize);
assert(b.free == 0);
// Fills the buffer with values 0..10
numberRead = fillBuffer(b[], b.free, 0, 10);
b += numberRead;
assert(b.free == b.blockSize - numberRead);
b.clear();
assert(b.free == b.blockSize);
defaultAllocator.dispose(b);
}
/**
@ -213,7 +188,7 @@ class ReadBuffer : Buffer
*
* Returns: $(D_KEYWORD this).
*/
ReadBuffer opOpAssign(string op)(size_t length)
ref ReadBuffer opOpAssign(string op)(in size_t length)
if (op == "+")
{
length_ += length;
@ -224,7 +199,7 @@ class ReadBuffer : Buffer
///
unittest
{
auto b = defaultAllocator.make!ReadBuffer;
ReadBuffer!ubyte b;
size_t numberRead;
ubyte[] result;
@ -251,16 +226,6 @@ class ReadBuffer : Buffer
assert(result[9] == 9);
assert(result[10] == 20);
assert(result[14] == 24);
defaultAllocator.dispose(b);
}
/**
* Returns: Length of available data.
*/
@property size_t opDollar() const pure nothrow @safe @nogc
{
return length;
}
/**
@ -270,7 +235,7 @@ class ReadBuffer : Buffer
*
* Returns: Array between $(D_PARAM start) and $(D_PARAM end).
*/
@property ubyte[] opSlice(size_t start, size_t end) pure nothrow @safe @nogc
T[] opSlice(in size_t start, in size_t end)
{
return buffer_[this.start + start .. this.start + end];
}
@ -282,7 +247,7 @@ class ReadBuffer : Buffer
*
* Returns: A free chunk of the buffer.
*/
ubyte[] opIndex()
T[] opIndex()
{
if (start > 0)
{
@ -294,7 +259,12 @@ class ReadBuffer : Buffer
{
if (capacity - length < minAvailable)
{
defaultAllocator.resizeArray!ubyte(buffer_, capacity + blockSize);
void[] buf = buffer_;
immutable cap = capacity;
() @trusted {
allocator.reallocate(buf, (cap + blockSize) * T.sizeof);
buffer_ = cast(T[]) buf;
}();
}
ring = length_;
return buffer_[length_ .. $];
@ -304,7 +274,7 @@ class ReadBuffer : Buffer
///
unittest
{
auto b = defaultAllocator.make!ReadBuffer;
ReadBuffer!ubyte b;
size_t numberRead;
ubyte[] result;
@ -318,81 +288,102 @@ class ReadBuffer : Buffer
assert(result[9] == 9);
b.clear();
assert(b.length == 0);
defaultAllocator.dispose(b);
}
mixin DefaultAllocator;
}
private unittest
{
static assert(is(ReadBuffer!int));
}
/**
* Circular, self-expanding buffer with overflow support. Can be used with
* functions returning returning the number of the transferred bytes.
* functions returning the number of the transferred bytes.
*
* The buffer is optimized for situations where you read all the data from it
* at once (without writing to it occasionally). It can become ineffective if
* you permanently keep some data in the buffer and alternate writing and
* reading, because it may allocate and move elements.
*
* Params:
* T = Buffer type.
*/
class WriteBuffer : Buffer
struct WriteBuffer(T = ubyte)
if (isScalarType!T)
{
/// Internal buffer.
protected ubyte[] buffer_;
private T[] buffer_;
/// Buffer start position.
protected size_t start;
private size_t start;
/// Buffer ring area size. After this position begins buffer overflow area.
protected size_t ring;
private size_t ring;
/// Size by which the buffer will grow.
protected immutable size_t blockSize;
private immutable size_t blockSize;
/// The position of the free area in the buffer.
protected size_t position;
private size_t position;
invariant
{
assert(blockSize > 0);
// position can refer to an element outside the buffer if the buffer is full.
// Position can refer to an element outside the buffer if the buffer is full.
assert(position <= buffer_.length);
}
/**
* Params:
* size = Initial buffer size and the size by which the buffer
* will grow.
* size = Initial buffer size and the size by which the buffer will
* grow.
* allocator = Allocator.
*
* Precondition: $(D_INLINECODE size > 0 && allocator !is null)
*/
this(size_t size = 8192)
this(in size_t size, shared Allocator allocator = defaultAllocator) @trusted
in
{
assert(size > 0);
assert(allocator !is null);
}
body
{
blockSize = size;
ring = size - 1;
defaultAllocator.resizeArray!ubyte(buffer_, size);
allocator_ = allocator;
buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
}
@disable this();
/**
* Deallocates the internal buffer.
*/
~this()
{
defaultAllocator.dispose(buffer_);
allocator.deallocate(buffer_);
}
/**
* Returns: The size of the internal buffer.
*/
@property size_t capacity() const @nogc @safe pure nothrow
@property size_t capacity() const
{
return buffer_.length;
}
/**
* Note that $(D_PSYMBOL length) doesn't return the real length of the data,
* but only the array length that will be returned with $(D_PSYMBOL buffer)
* next time. Be sure to call $(D_PSYMBOL buffer) and set $(D_KEYWORD +=)
* but only the array length that will be returned with $(D_PSYMBOL opIndex)
* next time. Be sure to call $(D_PSYMBOL opIndex) and set $(D_KEYWORD +=)
* until $(D_PSYMBOL length) returns 0.
*
* Returns: Data size.
*/
@property size_t length() const @nogc @safe pure nothrow
@property size_t length() const
{
if (position > ring || position < start) // Buffer overflowed
{
@ -404,18 +395,13 @@ class WriteBuffer : Buffer
}
}
/**
* Returns: Length of available data.
*/
@property size_t opDollar() const pure nothrow @safe @nogc
{
return length;
}
/// ditto
alias opDollar = length;
///
unittest
{
auto b = defaultAllocator.make!WriteBuffer(4);
auto b = WriteBuffer!ubyte(4);
ubyte[3] buf = [48, 23, 255];
b ~= buf;
@ -432,14 +418,12 @@ class WriteBuffer : Buffer
assert(b.length == 5);
b += b.length;
assert(b.length == 0);
defaultAllocator.dispose(b);
}
/**
* Returns: Available space.
*/
@property size_t free() const @nogc @safe pure nothrow
@property size_t free() const
{
return capacity - length;
}
@ -448,9 +432,9 @@ class WriteBuffer : Buffer
* Appends data to the buffer.
*
* Params:
* buffer = Buffer chunk got with $(D_PSYMBOL buffer).
* buffer = Buffer chunk got with $(D_PSYMBOL opIndex).
*/
WriteBuffer opOpAssign(string op)(ubyte[] buffer)
ref WriteBuffer opOpAssign(string op)(in T[] buffer)
if (op == "~")
{
size_t end, start;
@ -496,9 +480,12 @@ class WriteBuffer : Buffer
end = position + buffer.length - start;
if (end > capacity)
{
auto newSize = end / blockSize * blockSize + blockSize;
defaultAllocator.resizeArray!ubyte(buffer_, newSize);
auto newSize = (end / blockSize * blockSize + blockSize) * T.sizeof;
() @trusted {
void[] buf = buffer_;
allocator.reallocate(buf, newSize);
buffer_ = cast(T[]) buf;
}();
}
buffer_[position .. end] = buffer[start .. $];
position = end;
@ -514,7 +501,7 @@ class WriteBuffer : Buffer
///
unittest
{
auto b = defaultAllocator.make!WriteBuffer(4);
auto b = WriteBuffer!ubyte(4);
ubyte[3] buf = [48, 23, 255];
b ~= buf;
@ -532,31 +519,31 @@ class WriteBuffer : Buffer
assert(b.capacity == 8);
assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
&& b.buffer_[2] == 48 && b.buffer_[3] == 23 && b.buffer_[4] == 255);
}
defaultAllocator.dispose(b);
b = make!WriteBuffer(defaultAllocator, 2);
///
unittest
{
auto b = WriteBuffer!ubyte(2);
ubyte[3] buf = [48, 23, 255];
b ~= buf;
assert(b.start == 0);
assert(b.capacity == 4);
assert(b.ring == 3);
assert(b.position == 3);
defaultAllocator.dispose(b);
}
/**
* Sets how many bytes were written. It will shrink the buffer
* appropriately. Always set this property after calling
* $(D_PSYMBOL buffer).
* appropriately. Always call it after $(D_PSYMBOL opIndex).
*
* Params:
* length = Length of the written data.
*
* Returns: $(D_KEYWORD this).
*/
@property WriteBuffer opOpAssign(string op)(size_t length) pure nothrow @safe @nogc
ref WriteBuffer opOpAssign(string op)(in size_t length)
if (op == "+")
in
{
@ -583,9 +570,11 @@ class WriteBuffer : Buffer
{
auto overflow = position - afterRing;
if (overflow > length) {
buffer_[start.. start + length] = buffer_[afterRing.. afterRing + length];
buffer_[afterRing.. afterRing + length] = buffer_[afterRing + length ..position];
if (overflow > length)
{
immutable afterLength = afterRing + length;
buffer_[start .. start + length] = buffer_[afterRing .. afterLength];
buffer_[afterRing .. afterLength] = buffer_[afterLength .. position];
position -= length;
}
else if (overflow == length)
@ -620,7 +609,7 @@ class WriteBuffer : Buffer
///
unittest
{
auto b = defaultAllocator.make!WriteBuffer;
auto b = WriteBuffer!ubyte(6);
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
b ~= buf;
@ -629,8 +618,6 @@ class WriteBuffer : Buffer
assert(b.length == 4);
b += 4;
assert(b.length == 0);
defaultAllocator.dispose(b);
}
/**
@ -639,14 +626,14 @@ class WriteBuffer : Buffer
* After calling it, set $(D_KEYWORD +=) to the length could be
* written.
*
* $(D_PSYMBOL buffer) may return only part of the data. You may need
* to call it (and set $(D_KEYWORD +=) several times until
* $(D_PSYMBOL opIndex) may return only part of the data. You may need
* to call it and set $(D_KEYWORD +=) several times until
* $(D_PSYMBOL length) is 0. If all the data can be written,
* maximally 3 calls are required.
*
* Returns: A chunk of data buffer.
*/
@property ubyte[] opSlice(size_t start, size_t end) pure nothrow @safe @nogc
T[] opSlice(in size_t start, in size_t end)
{
immutable internStart = this.start + start;
@ -663,7 +650,7 @@ class WriteBuffer : Buffer
///
unittest
{
auto b = defaultAllocator.make!WriteBuffer(6);
auto b = WriteBuffer!ubyte(6);
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
b ~= buf;
@ -678,23 +665,28 @@ class WriteBuffer : Buffer
assert(b[0 .. $] == buf[0 .. 6]);
b += b.length;
defaultAllocator.dispose(b);
}
/**
* After calling it, set $(D_KEYWORD +=) to the length could be
* written.
*
* $(D_PSYMBOL buffer) may return only part of the data. You may need
* to call it (and set $(D_KEYWORD +=) several times until
* $(D_PSYMBOL opIndex) may return only part of the data. You may need
* to call it and set $(D_KEYWORD +=) several times until
* $(D_PSYMBOL length) is 0. If all the data can be written,
* maximally 3 calls are required.
*
* Returns: A chunk of data buffer.
*/
@property ubyte[] opIndex() pure nothrow @safe @nogc
T[] opIndex()
{
return opSlice(0, length);
}
mixin DefaultAllocator;
}
private unittest
{
static assert(is(typeof(WriteBuffer!int(5))));
}

View File

@ -0,0 +1,108 @@
/* 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/. */
/*
* Internal package used by containers that rely on entries/nodes.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/container/entry.d,
* tanya/container/entry.d)
*/
module tanya.container.entry;
import tanya.meta.trait;
import tanya.typecons;
package struct SEntry(T)
{
// Item content.
T content;
// Next item.
SEntry* next;
}
package struct DEntry(T)
{
// Item content.
T content;
// Previous and next item.
DEntry* next, prev;
}
package struct HashEntry(K, V)
{
this(ref K key, ref V value)
{
this.pair = Pair!(K, V)(key, value);
}
Pair!(K, V) pair;
HashEntry* next;
}
package enum BucketStatus : byte
{
deleted = -1,
empty = 0,
used = 1,
}
package struct Bucket(T)
{
@property void content(ref T content)
{
this.content_ = content;
this.status = BucketStatus.used;
}
@property ref inout(T) content() inout
{
return this.content_;
}
bool opEquals(ref T content)
{
if (this.status == BucketStatus.used && this.content == content)
{
return true;
}
return false;
}
bool opEquals(ref const T content) const
{
if (this.status == BucketStatus.used && this.content == content)
{
return true;
}
return false;
}
bool opEquals(ref typeof(this) that)
{
return this.content == that.content && this.status == that.status;
}
bool opEquals(ref typeof(this) that) const
{
return this.content == that.content && this.status == that.status;
}
void remove()
{
static if (hasElaborateDestructor!T)
{
destroy(this.content);
}
this.status = BucketStatus.deleted;
}
T content_;
BucketStatus status = BucketStatus.empty;
}

File diff suppressed because it is too large Load Diff

View File

@ -3,14 +3,42 @@
* 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-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/container/package.d,
* tanya/container/package.d)
*/
module tanya.container;
public import tanya.container.array;
public import tanya.container.buffer;
public import tanya.container.list;
public import tanya.container.vector;
public import tanya.container.queue;
public import tanya.container.set;
public import tanya.container.string;
/**
* Thrown if $(D_PSYMBOL Set) cannot insert a new element because the container
* is full.
*/
class HashContainerFullException : Exception
{
/**
* Params:
* msg = The message for the exception.
* file = The file where the exception occurred.
* line = The line number where the exception occurred.
* next = The previous exception in the chain of exceptions, if any.
*/
this(string msg,
string file = __FILE__,
size_t line = __LINE__,
Throwable next = null) @nogc @safe pure nothrow
{
super(msg, file, line, next);
}
}

View File

@ -3,103 +3,100 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Copyright: Eugene Wissner 2016.
* FIFO queue.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/container/queue.d,
* tanya/container/queue.d)
*/
module tanya.container.queue;
import tanya.algorithm.mutation;
import tanya.container.entry;
import tanya.exception;
import tanya.memory;
import tanya.meta.trait;
/**
* Queue.
* FIFO queue.
*
* Params:
* T = Content type.
*/
class Queue(T)
struct Queue(T)
{
/**
* Creates a new $(D_PSYMBOL Queue).
*
* Params:
* allocator = The allocator should be used for the element
* allocations.
*/
this(shared Allocator allocator = defaultAllocator)
{
this.allocator = allocator;
}
/**
* Removes all elements from the queue.
*/
~this()
{
foreach (e; this)
while (!empty)
{
static if (isFinalizable!T)
{
dispose(allocator, e);
}
dequeue();
}
}
/**
* Returns: First element.
* 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.
*/
@property ref T front()
in
size_t length() const
{
assert(!empty);
size_t len;
for (const(SEntry!T)* i = first; i !is null; i = i.next)
{
++len;
}
body
{
return first.next.content;
return len;
}
/**
* Inserts a new element.
*
* Params:
* x = New element.
*
* Returns: $(D_KEYWORD this).
*/
typeof(this) insertBack(T x)
///
unittest
{
Entry* temp = make!Entry(allocator);
Queue!int q;
temp.content = x;
assert(q.length == 0);
q.enqueue(5);
assert(q.length == 1);
q.enqueue(4);
assert(q.length == 2);
q.enqueue(9);
assert(q.length == 3);
q.dequeue();
assert(q.length == 2);
q.dequeue();
assert(q.length == 1);
q.dequeue();
assert(q.length == 0);
}
private void enqueueEntry(ref SEntry!T* entry)
{
if (empty)
{
first.next = rear = temp;
first = rear = entry;
}
else
{
rear.next = temp;
rear.next = entry;
rear = rear.next;
}
return this;
}
alias insert = insertBack;
///
unittest
private SEntry!T* allocateEntry()
{
auto q = make!(Queue!int)(defaultAllocator);
int[2] values = [8, 9];
q.insertBack(values[0]);
assert(q.front is values[0]);
q.insertBack(values[1]);
assert(q.front is values[0]);
dispose(defaultAllocator, q);
auto temp = cast(SEntry!T*) allocator.allocate(SEntry!T.sizeof);
if (temp is null)
{
onOutOfMemoryError();
}
return temp;
}
/**
@ -107,112 +104,187 @@ class Queue(T)
*
* Params:
* x = New element.
*
* Returns: $(D_KEYWORD this).
*/
typeof(this) opOpAssign(string Op)(ref T x)
if (Op == "~")
void enqueue(ref T x)
{
return insertBack(x);
auto temp = allocateEntry();
*temp = SEntry!T.init;
temp.content = x;
enqueueEntry(temp);
}
/// ditto
void enqueue(T x)
{
auto temp = allocateEntry();
moveEmplace(x, (*temp).content);
(*temp).next = null;
enqueueEntry(temp);
}
///
unittest
{
auto q = make!(Queue!int)(defaultAllocator);
int value = 5;
Queue!int q;
assert(q.empty);
q ~= value;
assert(q.front == value);
assert(!q.empty);
dispose(defaultAllocator, q);
q.enqueue(8);
q.enqueue(9);
assert(q.dequeue() == 8);
assert(q.dequeue() == 9);
}
/**
* Returns: $(D_KEYWORD true) if the queue is empty.
*/
@property bool empty() const @safe pure nothrow
@property bool empty() const
{
return first.next is null;
return first is null;
}
///
unittest
{
auto q = make!(Queue!int)(defaultAllocator);
Queue!int q;
int value = 7;
assert(q.empty);
q.insertBack(value);
q.enqueue(value);
assert(!q.empty);
dispose(defaultAllocator, q);
}
/**
* Move position to the next element.
* Move the position to the next element.
*
* Returns: $(D_KEYWORD this).
* Returns: Dequeued element.
*/
typeof(this) popFront()
T dequeue()
in
{
assert(!empty);
}
body
{
auto n = first.next.next;
auto n = first.next;
T ret = move(first.content);
dispose(allocator, first.next);
first.next = n;
return this;
allocator.dispose(first);
first = n;
return ret;
}
///
unittest
{
auto q = make!(Queue!int)(defaultAllocator);
int[2] values = [8, 9];
Queue!int q;
q.insertBack(values[0]);
q.insertBack(values[1]);
assert(q.front is values[0]);
q.popFront();
assert(q.front is values[1]);
dispose(defaultAllocator, q);
q.enqueue(8);
q.enqueue(9);
assert(q.dequeue() == 8);
assert(q.dequeue() == 9);
}
/**
* Queue entry.
* $(D_KEYWORD foreach) iteration. The elements will be automatically
* dequeued.
*
* Params:
* dg = $(D_KEYWORD foreach) body.
*
* Returns: The value returned from $(D_PARAM dg).
*/
protected struct Entry
int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
{
/// Queue item content.
T content;
int result;
/// Next list item.
Entry* next;
for (size_t i; !empty; ++i)
{
auto e = dequeue();
if ((result = dg(i, e)) != 0)
{
return result;
}
}
return result;
}
/// The first element of the list.
protected Entry first;
/// ditto
int opApply(scope int delegate(ref T) @nogc dg)
{
int result;
/// The last element of the list.
protected Entry* rear;
private shared Allocator allocator;
while (!empty)
{
auto e = dequeue();
if ((result = dg(e)) != 0)
{
return result;
}
}
return result;
}
///
unittest
{
auto q = make!(Queue!int)(defaultAllocator);
Queue!int q;
dispose(defaultAllocator, q);
size_t j;
q.enqueue(5);
q.enqueue(4);
q.enqueue(9);
foreach (i, e; q)
{
assert(i != 2 || e == 9);
assert(i != 1 || e == 4);
assert(i != 0 || e == 5);
++j;
}
assert(j == 3);
assert(q.empty);
j = 0;
q.enqueue(5);
q.enqueue(4);
q.enqueue(9);
foreach (e; q)
{
assert(j != 2 || e == 9);
assert(j != 1 || e == 4);
assert(j != 0 || e == 5);
++j;
}
assert(j == 3);
assert(q.empty);
}
private SEntry!T* first;
private SEntry!T* rear;
mixin DefaultAllocator;
}
///
unittest
{
Queue!int q;
q.enqueue(5);
assert(!q.empty);
q.enqueue(4);
q.enqueue(9);
assert(q.dequeue() == 5);
foreach (i, ref e; q)
{
assert(i != 0 || e == 4);
assert(i != 1 || e == 9);
}
assert(q.empty);
}

View File

@ -0,0 +1,719 @@
/* 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.
* 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.algorithm.mutation;
import tanya.container;
import tanya.container.entry;
import tanya.memory;
import tanya.meta.trait;
import tanya.meta.transform;
/**
* Bidirectional range that iterates over the $(D_PSYMBOL Set)'s values.
*
* Params:
* E = Element type.
*/
struct Range(E)
{
static if (isMutable!E)
{
private alias DataRange = Array!(Bucket!(Unqual!E)).Range;
}
else
{
private alias DataRange = Array!(Bucket!(Unqual!E)).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();
}
@property void popFront()
in
{
assert(!this.dataRange.empty);
assert(this.dataRange.front.status == BucketStatus.used);
}
out
{
assert(this.dataRange.empty
|| this.dataRange.back.status == BucketStatus.used);
}
body
{
do
{
dataRange.popFront();
}
while (!dataRange.empty && dataRange.front.status != BucketStatus.used);
}
@property void popBack()
in
{
assert(!this.dataRange.empty);
assert(this.dataRange.back.status == BucketStatus.used);
}
out
{
assert(this.dataRange.empty
|| this.dataRange.back.status == BucketStatus.used);
}
body
{
do
{
dataRange.popBack();
}
while (!dataRange.empty && dataRange.back.status != BucketStatus.used);
}
@property ref inout(E) front() inout
in
{
assert(!this.dataRange.empty);
assert(this.dataRange.front.status == BucketStatus.used);
}
body
{
return dataRange.front.content;
}
@property ref inout(E) back() inout
in
{
assert(!this.dataRange.empty);
assert(this.dataRange.back.status == BucketStatus.used);
}
body
{
return dataRange.back.content;
}
Range opIndex()
{
return typeof(return)(this.dataRange[]);
}
Range!(const E) 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.
*
* Currently works only with integral types.
*
* Params:
* T = Element type.
*/
struct Set(T)
if (isIntegral!T || is(Unqual!T == bool))
{
/// The range types for $(D_PSYMBOL Set).
alias Range = .Range!T;
/// ditto
alias ConstRange = .Range!(const T);
invariant
{
assert(this.lengthIndex < primes.length);
assert(this.data.length == 0
|| this.data.length == primes[this.lengthIndex]);
}
/**
* Constructor.
*
* Params:
* n = Minimum number of buckets.
* allocator = Allocator.
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
this(const size_t n, shared Allocator allocator = defaultAllocator)
in
{
assert(allocator !is null);
}
body
{
this(allocator);
rehash(n);
}
/// ditto
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
{
this.data = typeof(this.data)(allocator);
}
///
unittest
{
{
auto set = Set!int(defaultAllocator);
assert(set.capacity == 0);
}
{
auto set = Set!int(8);
assert(set.capacity == 13);
}
}
/**
* 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.
*/
this(S)(ref S init, shared Allocator allocator = defaultAllocator)
if (is(Unqual!S == Set))
in
{
assert(allocator !is null);
}
body
{
this.data = typeof(this.data)(init.data, allocator);
}
/// ditto
this(S)(S init, shared Allocator allocator = defaultAllocator)
if (is(S == Set))
in
{
assert(allocator !is null);
}
body
{
this.data = typeof(this.data)(move(init.data), allocator);
this.lengthIndex = init.lengthIndex;
init.lengthIndex = 0;
}
/**
* 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;
this.lengthIndex = that.lengthIndex;
return this;
}
/// ditto
ref typeof(this) opAssign(S)(S that) @trusted
if (is(S == Set))
{
swap(this.data, that.data);
swap(this.lengthIndex, that.lengthIndex);
return this;
}
/**
* Returns: Used allocator.
*
* Postcondition: $(D_INLINECODE allocator !is null)
*/
@property shared(Allocator) allocator() const
out (allocator)
{
assert(allocator !is null);
}
body
{
return cast(shared Allocator) this.data.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.length;
}
///
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
{
size_t count;
foreach (ref e; this.data[])
{
if (e.status == BucketStatus.used)
{
++count;
}
}
return count;
}
///
unittest
{
Set!int set;
assert(set.length == 0);
set.insert(8);
assert(set.length == 1);
}
private static const size_t[41] primes = [
3, 7, 13, 23, 29, 37, 53, 71, 97, 131, 163, 193, 239, 293, 389, 521,
769, 919, 1103, 1327, 1543, 2333, 3079, 4861, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469,
12582917, 25165843, 139022417, 282312799, 573292817, 1164186217,
];
/// The maximum number of buckets the container can have.
enum size_t maxBucketCount = primes[$ - 1];
static private size_t calculateHash(U)(ref const U value)
if (is(U == Unqual!T))
{
static if (isIntegral!T || isSomeChar!T || is(T == bool))
{
return (cast(size_t) value);
}
else
{
static assert(false);
}
}
static private size_t locateBucket(ref const DataType buckets,
const size_t hash)
in
{
assert(buckets.length > 0);
}
body
{
return hash % buckets.length;
}
/*
* Returns bucket position for `hash`. `0` may mean the 0th position or an
* empty `buckets` array.
*/
private size_t locateBucket(const size_t hash) const
{
return this.data.length == 0 ? 0 : locateBucket(this.data, hash);
}
private enum InsertStatus : byte
{
found = -1,
failed = 0,
added = 1,
}
/*
* Inserts the value in an empty or deleted bucket. If the value is
* already in there, does nothing and returns InsertStatus.found. If the
* hash array is full returns InsertStatus.failed. Otherwise,
* InsertStatus.added is returned.
*/
private InsertStatus insertInUnusedBucket(ref T value)
{
auto bucketPosition = locateBucket(this.data, calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e == value) // Already in the set.
{
return InsertStatus.found;
}
else if (e.status != BucketStatus.used) // Insert the value.
{
e.content = value;
return InsertStatus.added;
}
}
return InsertStatus.failed;
}
/**
* Inserts a new element.
*
* Params:
* value = Element value.
*
* Returns: Amount of new elements inserted.
*
* Throws: $(D_PSYMBOL HashContainerFullException) if the insertion failed.
*/
size_t insert(T value)
{
if (this.data.length == 0)
{
this.data = DataType(primes[0], allocator);
}
InsertStatus status = insertInUnusedBucket(value);
for (; !status; status = insertInUnusedBucket(value))
{
if (this.primes.length == (this.lengthIndex + 1))
{
throw make!HashContainerFullException(defaultAllocator,
"Set is full");
}
rehashToSize(this.lengthIndex + 1);
}
return status == InsertStatus.added;
}
///
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);
}
/**
* 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)
{
auto bucketPosition = locateBucket(calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e == value) // Found.
{
e.remove();
return 1;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return 0;
}
///
@nogc unittest
{
Set!int set;
assert(8 !in 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:
* value = Element to be searched for.
*
* Returns: $(D_KEYWORD true) if the given element exists in the container,
* $(D_KEYWORD false) otherwise.
*/
bool opBinaryRight(string op : "in")(auto ref const T value) const
{
auto bucketPosition = locateBucket(calculateHash(value));
foreach (ref e; this.data[bucketPosition .. $])
{
if (e == value) // Found.
{
return true;
}
else if (e.status == BucketStatus.empty)
{
break;
}
}
return false;
}
///
@nogc 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 capacity)
* and lower than or equal to $(D_PSYMBOL maxBucketCount), a rehash is
* forced.
*
* If $(D_PARAM n) is greater than $(D_PSYMBOL maxBucketCount),
* $(D_PSYMBOL maxBucketCount) is used instead as a new number of buckets.
*
* If $(D_PARAM n) is equal to the current $(D_PSYMBOL capacity), rehashing
* is forced without resizing the container.
*
* If $(D_PARAM n) is lower than the current $(D_PSYMBOL capacity), the
* function may have no effect.
*
* Rehashing is automatically performed whenever the container needs space
* to insert new elements.
*
* Params:
* n = Minimum number of buckets.
*/
void rehash(const size_t n)
{
size_t lengthIndex;
for (; lengthIndex < primes.length; ++lengthIndex)
{
if (primes[lengthIndex] >= n)
{
break;
}
}
rehashToSize(lengthIndex);
}
// Takes an index in the primes array.
private void rehashToSize(const size_t n)
{
auto storage = DataType(primes[n], allocator);
DataLoop: foreach (ref e1; this.data[])
{
if (e1.status == BucketStatus.used)
{
auto bucketPosition = locateBucket(storage,
calculateHash(e1.content));
foreach (ref e2; storage[bucketPosition .. $])
{
if (e2.status != BucketStatus.used) // Insert the value.
{
e2.content = e1.content;
continue DataLoop;
}
}
return; // Rehashing failed.
}
}
move(storage, this.data);
this.lengthIndex = n;
}
/**
* Returns: A bidirectional range that iterates over the $(D_PSYMBOL Set)'s
* elements.
*/
Range opIndex()
{
return typeof(return)(this.data[]);
}
/// ditto
ConstRange opIndex() const
{
return typeof(return)(this.data[]);
}
///
@nogc unittest
{
Set!int set;
assert(set[].empty);
set.insert(8);
assert(!set[].empty);
assert(set[].front == 8);
assert(set[].back == 8);
set.remove(8);
assert(set[].empty);
}
private @nogc unittest
{
const Set!int set;
assert(set[].empty);
}
private @nogc unittest
{
Set!int set;
set.insert(8);
auto r1 = set[];
auto r2 = r1.save();
r1.popFront();
assert(r1.empty);
r2.popBack();
assert(r2.empty);
}
private alias DataType = Array!(Bucket!T);
private DataType data;
private size_t lengthIndex;
}
// Basic insertion logic.
private @nogc unittest
{
Set!int set;
assert(set.insert(5) == 1);
assert(set.data[0].status == BucketStatus.empty);
assert(set.data[1].status == BucketStatus.empty);
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
assert(set.data.length == 3);
assert(set.insert(5) == 0);
assert(set.data[0].status == BucketStatus.empty);
assert(set.data[1].status == BucketStatus.empty);
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
assert(set.data.length == 3);
assert(set.insert(9) == 1);
assert(set.data[0].content == 9 && set.data[0].status == BucketStatus.used);
assert(set.data[1].status == BucketStatus.empty);
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
assert(set.data.length == 3);
assert(set.insert(7) == 1);
assert(set.insert(8) == 1);
assert(set.data[0].content == 7);
assert(set.data[1].content == 8);
assert(set.data[2].content == 9);
assert(set.data[3].status == BucketStatus.empty);
assert(set.data[5].content == 5);
assert(set.data.length == 7);
assert(set.insert(16) == 1);
assert(set.data[2].content == 9);
assert(set.data[3].content == 16);
assert(set.data[4].status == BucketStatus.empty);
}
// Static checks.
private unittest
{
import tanya.range.primitive;
static assert(isBidirectionalRange!(Set!int.ConstRange));
static assert(isBidirectionalRange!(Set!int.Range));
static assert(!isInfinite!(Set!int.Range));
static assert(!hasLength!(Set!int.Range));
static assert(is(Set!uint));
static assert(is(Set!long));
static assert(is(Set!ulong));
static assert(is(Set!short));
static assert(is(Set!ushort));
static assert(is(Set!bool));
}

File diff suppressed because it is too large Load Diff

View File

@ -1,417 +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.vector;
import tanya.memory;
/**
* One dimensional array. It allocates automatically if needed.
*
* If you assign a value:
* ---
* auto v = make!(Vector!int)(defaultAllocator);
* int value = 5;
*
* v[1000] = value;
*
* dispose(defaultAllocator, v);
* ---
* it will allocate not only for one, but for 1000 elements. So this
* implementation is more suitable for sequential data with random access.
*
* Params:
* T = Content type.
*/
class Vector(T)
{
/**
* Creates a new $(D_PSYMBOL Vector).
*
* Params:
* length = Initial length.
* allocator = The allocator should be used for the element
* allocations.
*/
this(size_t length, shared Allocator allocator = defaultAllocator)
{
this.allocator = allocator;
vector = makeArray!T(allocator, length);
}
/// Ditto.
this(shared Allocator allocator = defaultAllocator)
{
this(0, allocator);
}
/**
* Removes all elements from the vector.
*/
~this()
{
dispose(allocator, vector);
}
/**
* Returns: Vector length.
*/
@property size_t length() const
{
return vector.length;
}
/**
* Expans/shrinks the vector.
*
* Params:
* length = New length.
*/
@property void length(size_t length)
{
resizeArray!T(allocator, vector, length);
}
///
unittest
{
auto v = make!(Vector!int)(defaultAllocator);
v.length = 5;
assert(v.length == 5);
// TODO
v.length = 7;
assert(v.length == 7);
v.length = 0;
assert(v.length == 0);
dispose(defaultAllocator, v);
}
/**
* Returns: $(D_KEYWORD true) if the vector is empty.
*/
@property bool empty() const
{
return length == 0;
}
static if (isFinalizable!T)
{
/**
* Removes an elements from the vector.
*
* Params:
* pos = Element index.
*/
void remove(size_t pos)
{
auto el = vector[pos];
dispose(allocator, el);
}
}
/**
* Assigns a value. Allocates if needed.
*
* Params:
* value = Value.
*
* Returns: Assigned value.
*/
T opIndexAssign(T value, size_t pos)
{
if (pos >= length)
{
resizeArray!T(allocator, vector, pos + 1);
}
return vector[pos] = value;
}
///
unittest
{
auto v = make!(Vector!int)(defaultAllocator);
int[2] values = [5, 15];
assert(v.length == 0);
v[1] = values[0];
assert(v.length == 2);
v[3] = values[0];
assert(v.length == 4);
v[4] = values[1];
assert(v.length == 5);
dispose(defaultAllocator, v);
}
/**
* Returns: The value on index $(D_PARAM pos).
*/
ref T opIndex(in size_t pos)
in
{
assert(length > pos);
}
body
{
return vector[pos];
}
///
unittest
{
auto v = make!(Vector!int)(defaultAllocator);
int[2] values = [5, 15];
v[1] = values[0];
assert(v[1] is values[0]);
v[3] = values[0];
assert(v[3] is values[0]);
v[4] = values[1];
assert(v[4] is values[1]);
v[0] = values[1];
assert(v[0] is values[1]);
dispose(defaultAllocator, v);
}
/**
* $(D_KEYWORD foreach) iteration.
*
* Params:
* dg = $(D_KEYWORD foreach) body.
*/
int opApply(int delegate(ref T) dg)
{
int result;
foreach (e; vector)
{
result = dg(e);
if (result != 0)
{
return result;
}
}
return result;
}
/// Ditto.
int opApply(int delegate(ref size_t i, ref T) dg)
{
int result;
foreach (i, e; vector)
{
result = dg(i, e);
if (result != 0)
{
return result;
}
}
return result;
}
///
unittest
{
auto v = make!(Vector!int)(defaultAllocator, 1);
int[3] values = [5, 15, 8];
v[0] = values[0];
v[1] = values[1];
v[2] = values[2];
int i;
foreach (e; v)
{
assert(i != 0 || e is values[0]);
assert(i != 1 || e is values[1]);
assert(i != 2 || e is values[2]);
++i;
}
foreach (j, e; v)
{
assert(j != 0 || e is values[0]);
assert(j != 1 || e is values[1]);
assert(j != 2 || e is values[2]);
}
dispose(defaultAllocator, v);
}
/**
* Sets the first element. Allocates if the vector is empty.
*
* Params:
* x = New element.
*/
@property void front(ref T x)
{
this[0] = x;
}
/**
* Returns: The first element.
*/
@property ref inout(T) front() inout
in
{
assert(!empty);
}
body
{
return vector[0];
}
///
unittest
{
auto v = make!(Vector!int)(defaultAllocator, 1);
int[2] values = [5, 15];
v.front = values[0];
assert(v.front == 5);
v.front = values[1];
assert(v.front == 15);
dispose(defaultAllocator, v);
}
/**
* Move position to the next element.
*
* Returns: $(D_KEYWORD this).
*/
typeof(this) popFront()
in
{
assert(!empty);
}
body
{
vector[0 .. $ - 1] = vector[1..$];
resizeArray(allocator, vector, length - 1);
return this;
}
///
unittest
{
auto v = make!(Vector!int)(defaultAllocator, 1);
int[2] values = [5, 15];
v[0] = values[0];
v[1] = values[1];
assert(v.front is values[0]);
assert(v.length == 2);
v.popFront();
assert(v.front is values[1]);
assert(v.length == 1);
v.popFront();
assert(v.empty);
dispose(defaultAllocator, v);
}
/**
* Sets the last element. Allocates if the vector is empty.
*
* Params:
* x = New element.
*/
@property void back(ref T x)
{
vector[empty ? 0 : $ - 1] = x;
}
/**
* Returns: The last element.
*/
@property ref inout(T) back() inout
in
{
assert(!empty);
}
body
{
return vector[$ - 1];
}
///
unittest
{
auto v = make!(Vector!int)(defaultAllocator, 1);
int[2] values = [5, 15];
v.back = values[0];
assert(v.back == 5);
v.back = values[1];
assert(v.back == 15);
dispose(defaultAllocator, v);
}
/**
* Move position to the previous element.
*
* Returns: $(D_KEYWORD this).
*/
typeof(this) popBack()
in
{
assert(!empty);
}
body
{
resizeArray(allocator, vector, length - 1);
return this;
}
///
unittest
{
auto v = make!(Vector!int)(defaultAllocator, 1);
int[2] values = [5, 15];
v[0] = values[0];
v[1] = values[1];
assert(v.back is values[1]);
assert(v.length == 2);
v.popBack();
assert(v.back is values[0]);
assert(v.length == 1);
v.popBack();
assert(v.empty);
dispose(defaultAllocator, v);
}
/// Container.
protected T[] vector;
private shared Allocator allocator;
}
///
unittest
{
auto v = make!(Vector!int)(defaultAllocator);
dispose(defaultAllocator, v);
}

709
source/tanya/conv.d Normal file
View File

@ -0,0 +1,709 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This module provides functions for converting between different types.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/conv.d,
* tanya/conv.d)
*/
module tanya.conv;
import tanya.container.string;
import tanya.format;
import tanya.memory;
import tanya.memory.op;
import tanya.meta.trait;
import tanya.meta.transform;
version (unittest)
{
import tanya.test.assertion;
}
/**
* Constructs a new object of type $(D_PARAM T) in $(D_PARAM memory) with the
* given arguments.
*
* If $(D_PARAM T) is a $(D_KEYWORD class), emplace returns a class reference
* of type $(D_PARAM T), otherwise a pointer to the constructed object is
* returned.
*
* If $(D_PARAM T) is a nested class inside another class, $(D_PARAM outer)
* should be an instance of the outer class.
*
* $(D_PARAM args) are arguments for the constructor of $(D_PARAM T). If
* $(D_PARAM T) isn't an aggregate type and doesn't have a constructor,
* $(D_PARAM memory) can be initialized to `args[0]` if `Args.length == 1`,
* `Args[0]` should be implicitly convertible to $(D_PARAM T) then.
*
* Params:
* T = Constructed type.
* U = Type of the outer class if $(D_PARAM T) is a nested class.
* Args = Types of the constructor arguments if $(D_PARAM T) has a constructor
* or the type of the initial value.
* outer = Outer class instance if $(D_PARAM T) is a nested class.
* args = Constructor arguments if $(D_PARAM T) has a constructor or the
* initial value.
*
* Returns: New instance of type $(D_PARAM T) constructed in $(D_PARAM memory).
*
* Precondition: `memory.length == stateSize!T`.
* Postcondition: $(D_PARAM memory) and the result point to the same memory.
*/
T emplace(T, U, Args...)(void[] memory, U outer, auto ref Args args)
if (!isAbstractClass!T && isInnerClass!T && is(typeof(T.outer) == U))
in
{
assert(memory.length >= stateSize!T);
}
out (result)
{
assert(memory.ptr is (() @trusted => cast(void*) result)());
}
body
{
copy(typeid(T).initializer, memory);
auto result = (() @trusted => cast(T) memory.ptr)();
result.outer = outer;
static if (is(typeof(result.__ctor(args))))
{
result.__ctor(args);
}
return result;
}
/// ditto
T emplace(T, Args...)(void[] memory, auto ref Args args)
if (is(T == class) && !isAbstractClass!T && !isInnerClass!T)
in
{
assert(memory.length == stateSize!T);
}
out (result)
{
assert(memory.ptr is (() @trusted => cast(void*) result)());
}body
{
copy(typeid(T).initializer, memory);
auto result = (() @trusted => cast(T) memory.ptr)();
static if (is(typeof(result.__ctor(args))))
{
result.__ctor(args);
}
return result;
}
///
@nogc nothrow pure @safe unittest
{
import tanya.memory : stateSize;
class C
{
int i = 5;
class Inner
{
int i;
this(int param) pure nothrow @safe @nogc
{
this.i = param;
}
}
}
ubyte[stateSize!C] memory1;
ubyte[stateSize!(C.Inner)] memory2;
auto c = emplace!C(memory1);
assert(c.i == 5);
auto inner = emplace!(C.Inner)(memory2, c, 8);
assert(c.i == 5);
assert(inner.i == 8);
assert(inner.outer is c);
}
/// ditto
T* emplace(T, Args...)(void[] memory, auto ref Args args)
if (!isAggregateType!T && (Args.length <= 1))
in
{
assert(memory.length >= T.sizeof);
}
out (result)
{
assert(memory.ptr is result);
}
body
{
auto result = (() @trusted => cast(T*) memory.ptr)();
static if (Args.length == 1)
{
*result = T(args[0]);
}
else
{
*result = T.init;
}
return result;
}
/// ditto
T* emplace(T, Args...)(void[] memory, auto ref Args args)
if (!isPolymorphicType!T && isAggregateType!T)
in
{
assert(memory.length >= T.sizeof);
}
out (result)
{
assert(memory.ptr is result);
}
body
{
auto result = (() @trusted => cast(T*) memory.ptr)();
static if (!hasElaborateAssign!T && isAssignable!T)
{
*result = T.init;
}
else
{
static const T init = T.init;
copy((cast(void*) &init)[0 .. T.sizeof], memory);
}
static if (Args.length == 0)
{
static assert(is(typeof({ static T t; })),
"Default constructor is disabled");
}
else static if (is(typeof(T(args))))
{
*result = T(args);
}
else static if (is(typeof(result.__ctor(args))))
{
result.__ctor(args);
}
else
{
static assert(false,
"Unable to construct value with the given arguments");
}
return result;
}
///
@nogc nothrow pure @safe unittest
{
ubyte[4] memory;
auto i = emplace!int(memory);
static assert(is(typeof(i) == int*));
assert(*i == 0);
i = emplace!int(memory, 5);
assert(*i == 5);
static struct S
{
int i;
@disable this();
@disable this(this);
this(int i) @nogc nothrow pure @safe
{
this.i = i;
}
}
auto s = emplace!S(memory, 8);
static assert(is(typeof(s) == S*));
assert(s.i == 8);
}
// Handles "Cannot access frame pointer" error.
@nogc nothrow pure @safe unittest
{
struct F
{
~this() @nogc nothrow pure @safe
{
}
}
static assert(is(typeof(emplace!F((void[]).init))));
}
/**
* Thrown if a type conversion fails.
*/
final class ConvException : Exception
{
/**
* Params:
* msg = The message for the exception.
* file = The file where the exception occurred.
* line = The line number where the exception occurred.
* next = The previous exception in the chain of exceptions, if any.
*/
this(string msg,
string file = __FILE__,
size_t line = __LINE__,
Throwable next = null) @nogc @safe pure nothrow
{
super(msg, file, line, next);
}
}
/**
* If the source type $(D_PARAM From) and the target type $(D_PARAM To) are
* equal, does nothing. If $(D_PARAM From) can be implicitly converted to
* $(D_PARAM To), just returns $(D_PARAM from).
*
* Params:
* To = Target type.
*
* Returns: $(D_PARAM from).
*/
template to(To)
{
/**
* Params:
* From = Source type.
* from = Source value.
*/
ref To to(From)(ref From from)
if (is(To == From))
{
return from;
}
/// ditto
To to(From)(From from)
if (is(Unqual!To == Unqual!From) || (isNumeric!From && isFloatingPoint!To))
{
return from;
}
}
///
@nogc nothrow pure @safe unittest
{
auto val = 5.to!int();
assert(val == 5);
static assert(is(typeof(val) == int));
}
@nogc nothrow pure @safe unittest
{
int val = 5;
assert(val.to!int() == 5);
}
/**
* Performs checked conversion from an integral type $(D_PARAM From) to an
* integral type $(D_PARAM To).
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_PARAM from) converted to $(D_PARAM To).
*
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) is too small or too
* large to be represented by $(D_PARAM To).
*/
To to(To, From)(From from)
if (isIntegral!From
&& isIntegral!To
&& !is(Unqual!To == Unqual!From)
&& !is(To == enum))
{
static if ((isUnsigned!From && isSigned!To && From.sizeof == To.sizeof)
|| From.sizeof > To.sizeof)
{
if (from > To.max)
{
throw make!ConvException(defaultAllocator,
"Positive integer overflow");
}
}
static if (isSigned!From)
{
static if (isUnsigned!To)
{
if (from < 0)
{
throw make!ConvException(defaultAllocator,
"Negative integer overflow");
}
}
else static if (From.sizeof > To.sizeof)
{
if (from < To.min)
{
throw make!ConvException(defaultAllocator,
"Negative integer overflow");
}
}
}
static if (From.sizeof <= To.sizeof)
{
return from;
}
else static if (isSigned!To)
{
return cast(To) from;
}
else
{
return from & To.max;
}
}
@nogc nothrow pure @safe unittest
{
// ubyte -> ushort
assert((cast(ubyte) 0).to!ushort == 0);
assert((cast(ubyte) 1).to!ushort == 1);
assert((cast(ubyte) (ubyte.max - 1)).to!ushort == ubyte.max - 1);
assert((cast(ubyte) ubyte.max).to!ushort == ubyte.max);
// ubyte -> short
assert((cast(ubyte) 0).to!short == 0);
assert((cast(ubyte) 1).to!short == 1);
assert((cast(ubyte) (ubyte.max - 1)).to!short == ubyte.max - 1);
assert((cast(ubyte) ubyte.max).to!short == ubyte.max);
}
@nogc pure @safe unittest
{
// ubyte <- ushort
assert((cast(ushort) 0).to!ubyte == 0);
assert((cast(ushort) 1).to!ubyte == 1);
assert((cast(ushort) (ubyte.max - 1)).to!ubyte == ubyte.max - 1);
assert((cast(ushort) ubyte.max).to!ubyte == ubyte.max);
// ubyte <- short
assert((cast(short) 0).to!ubyte == 0);
assert((cast(short) 1).to!ubyte == 1);
assert((cast(short) (ubyte.max - 1)).to!ubyte == ubyte.max - 1);
assert((cast(short) ubyte.max).to!ubyte == ubyte.max);
// short <-> int
assert(short.min.to!int == short.min);
assert((short.min + 1).to!int == short.min + 1);
assert((cast(short) -1).to!int == -1);
assert((cast(short) 0).to!int == 0);
assert((cast(short) 1).to!int == 1);
assert((short.max - 1).to!int == short.max - 1);
assert(short.max.to!int == short.max);
assert((cast(int) short.min).to!short == short.min);
assert((cast(int) short.min + 1).to!short == short.min + 1);
assert((cast(int) -1).to!short == -1);
assert((cast(int) 0).to!short == 0);
assert((cast(int) 1).to!short == 1);
assert((cast(int) short.max - 1).to!short == short.max - 1);
assert((cast(int) short.max).to!short == short.max);
// uint <-> int
assert((cast(uint) 0).to!int == 0);
assert((cast(uint) 1).to!int == 1);
assert((cast(uint) (int.max - 1)).to!int == int.max - 1);
assert((cast(uint) int.max).to!int == int.max);
assert((cast(int) 0).to!uint == 0);
assert((cast(int) 1).to!uint == 1);
assert((cast(int) (int.max - 1)).to!uint == int.max - 1);
assert((cast(int) int.max).to!uint == int.max);
}
@nogc pure @safe unittest
{
assertThrown!ConvException(&to!(short, int), int.min);
assertThrown!ConvException(&to!(short, int), int.max);
assertThrown!ConvException(&to!(ushort, uint), uint.max);
assertThrown!ConvException(&to!(uint, int), -1);
}
@nogc nothrow pure @safe unittest
{
enum Test : int
{
one,
two,
}
assert(Test.one.to!int == 0);
assert(Test.two.to!int == 1);
}
/**
* Converts a floating point number to an integral type.
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: Truncated $(D_PARAM from) (everything after the decimal point is
* dropped).
*
* Throws: $(D_PSYMBOL ConvException) if
* $(D_INLINECODE from < To.min || from > To.max).
*/
To to(To, From)(From from)
if (isFloatingPoint!From
&& isIntegral!To
&& !is(Unqual!To == Unqual!From)
&& !is(To == enum))
{
if (from > To.max)
{
throw make!ConvException(defaultAllocator,
"Positive number overflow");
}
else if (from < To.min)
{
throw make!ConvException(defaultAllocator,
"Negative number overflow");
}
return cast(To) from;
}
///
@nogc pure @safe unittest
{
assert(1.5.to!int == 1);
assert(2147483646.5.to!int == 2147483646);
assert((-2147483647.5).to!int == -2147483647);
assert(2147483646.5.to!uint == 2147483646);
}
@nogc pure @safe unittest
{
assertThrown!ConvException(&to!(int, double), 2147483647.5);
assertThrown!ConvException(&to!(int, double), -2147483648.5);
assertThrown!ConvException(&to!(uint, double), -21474.5);
}
/**
* Performs checked conversion from an integral type $(D_PARAM From) to an
* $(D_KEYWORD enum).
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_KEYWORD enum) value.
*
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) is not a member of
* $(D_PSYMBOL To).
*/
To to(To, From)(From from)
if (isIntegral!From && is(To == enum))
{
foreach (m; EnumMembers!To)
{
if (from == m)
{
return m;
}
}
throw make!ConvException(defaultAllocator,
"Value not found in enum '" ~ To.stringof ~ "'");
}
///
@nogc pure @safe unittest
{
enum Test : int
{
one,
two,
}
static assert(is(typeof(1.to!Test) == Test));
assert(0.to!Test == Test.one);
assert(1.to!Test == Test.two);
}
@nogc pure @safe unittest
{
enum Test : uint
{
one,
two,
}
assertThrown!ConvException(&to!(Test, int), 5);
}
/**
* Converts $(D_PARAM from) to a boolean.
*
* If $(D_PARAM From) is a numeric type, then `1` becomes $(D_KEYWORD true),
* `0` $(D_KEYWORD false). Otherwise $(D_PSYMBOL ConvException) is thrown.
*
* If $(D_PARAM To) is a string (built-in string or $(D_PSYMBOL String)),
* then `"true"` or `"false"` are converted to the appropriate boolean value.
* Otherwise $(D_PSYMBOL ConvException) is thrown.
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_KEYWORD from) converted to a boolean.
*
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) isn't convertible.
*/
To to(To, From)(From from)
if (isNumeric!From && is(Unqual!To == bool) && !is(Unqual!To == Unqual!From))
{
if (from == 0)
{
return false;
}
else if (from < 0)
{
throw make!ConvException(defaultAllocator,
"Negative number overflow");
}
else if (from <= 1)
{
return true;
}
throw make!ConvException(defaultAllocator,
"Positive number overflow");
}
///
@nogc pure @safe unittest
{
assert(!0.0.to!bool);
assert(0.2.to!bool);
assert(0.5.to!bool);
assert(1.0.to!bool);
assert(!0.to!bool);
assert(1.to!bool);
}
@nogc pure @safe unittest
{
assertThrown!ConvException(&to!(bool, int), -1);
assertThrown!ConvException(&to!(bool, int), 2);
}
/// ditto
To to(To, From)(auto ref const From from)
if ((is(From == String) || isSomeString!From) && is(Unqual!To == bool))
{
if (from == "true")
{
return true;
}
else if (from == "false")
{
return false;
}
throw make!ConvException(defaultAllocator,
"String doesn't contain a boolean value");
}
///
@nogc pure @safe unittest
{
assert("true".to!bool);
assert(!"false".to!bool);
assert(String("true").to!bool);
assert(!String("false").to!bool);
}
@nogc pure @safe unittest
{
assertThrown!ConvException(() => "1".to!bool);
}
/**
* Converts a boolean to $(D_PARAM To).
*
* If $(D_PARAM To) is a numeric type, then $(D_KEYWORD true) becomes `1`,
* $(D_KEYWORD false) `0`.
*
* If $(D_PARAM To) is a $(D_PSYMBOL String), then `"true"` or `"false"`
* is returned.
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_PARAM from) converted to $(D_PARAM To).
*/
To to(To, From)(From from)
if (is(Unqual!From == bool) && isNumeric!To && !is(Unqual!To == Unqual!From))
{
return from;
}
///
@nogc nothrow pure @safe unittest
{
assert(true.to!float == 1.0);
assert(true.to!double == 1.0);
assert(true.to!ubyte == 1);
assert(true.to!byte == 1);
assert(true.to!ushort == 1);
assert(true.to!short == 1);
assert(true.to!uint == 1);
assert(true.to!int == 1);
assert(false.to!float == 0);
assert(false.to!double == 0);
assert(false.to!ubyte == 0);
assert(false.to!byte == 0);
assert(false.to!ushort == 0);
assert(false.to!short == 0);
assert(false.to!uint == 0);
assert(false.to!int == 0);
}
/**
* Converts $(D_PARAM From) to a $(D_PSYMBOL String).
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_PARAM from) converted to $(D_PSYMBOL String).
*/
To to(To, From)(auto ref From from)
if (is(Unqual!To == String))
{
return format!"{}"(from);
}
///
@nogc nothrow pure @safe unittest
{
assert(true.to!String == "true");
assert(false.to!String == "false");
}
@nogc nothrow pure @safe unittest
{
static assert(is(typeof((const String("true")).to!bool)));
static assert(is(typeof(false.to!(const String) == "false")));
}

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.container.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.expandArray(input, needed);
break;
case pkcs7:
if (needed)
{
allocator.expandArray(input, needed);
input[input.length - needed ..$].each!((ref e) => e = needed);
}
else
{
allocator.expandArray(input, blockSize);
}
break;
case ansiX923:
allocator.expandArray(input, 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.shrinkArray(input, 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.
* 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;
const string fullHexDigits = "0123456789ABCDEFabcdef"; /// 0..9A..Fa..f.
const string hexDigits = "0123456789ABCDEF"; /// 0..9A..F.
const string lowerHexDigits = "0123456789abcdef"; /// 0..9a..f.
const string digits = "0123456789"; /// 0..9.
const string octalDigits = "01234567"; /// 0..7.
/// A..Za..z.
const string letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const string uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; /// A..Z.
const string lowercase = "abcdefghijklmnopqrstuvwxyz"; /// a..z.
/**
* Whitespace, Horizontal Tab (HT), Line Feed (LF), Carriage Return (CR),
* Vertical Tab (VT) or Form Feed (FF).
*/
const string whitespace = "\t\n\v\f\r ";
/// 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');
}
///
pure nothrow @safe @nogc 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');
}
///
pure nothrow @safe @nogc 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);
}
///
pure nothrow @safe @nogc 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');
}
///
pure nothrow @safe @nogc 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);
}
///
pure nothrow @safe @nogc 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;
}
///
pure nothrow @safe @nogc 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);
}
///
pure nothrow @safe @nogc 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);
}
///
pure nothrow @safe @nogc 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);
}
///
pure nothrow @safe @nogc 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);
}
///
pure nothrow @safe @nogc 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'));
}
///
pure nothrow @safe @nogc 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');
}
///
pure nothrow @safe @nogc 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));
}
///
pure nothrow @safe @nogc 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)(const C c)
if (isSomeChar!C)
{
return isLower(c) ? (cast(C) (c - 32)) : c;
}
///
pure nothrow @safe @nogc 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)(const C c)
if (isSomeChar!C)
{
return isUpper(c) ? (cast(C) (c + 32)) : c;
}
///
pure nothrow @safe @nogc 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.
* 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.
* 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);
}

293
source/tanya/format/conv.d Normal file
View File

@ -0,0 +1,293 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This module provides functions for converting between different types.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: Jeff Roberts, $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/format/conv.d,
* tanya/format/conv.d)
*/
module tanya.format.conv;
import tanya.container.string;
import tanya.memory;
import tanya.memory.op;
import tanya.meta.trait;
import tanya.meta.transform;
version (unittest)
{
import tanya.test.assertion;
}
/**
* Thrown if a type conversion fails.
*/
final class ConvException : Exception
{
/**
* Params:
* msg = The message for the exception.
* file = The file where the exception occurred.
* line = The line number where the exception occurred.
* next = The previous exception in the chain of exceptions, if any.
*/
this(string msg,
string file = __FILE__,
size_t line = __LINE__,
Throwable next = null) @nogc @safe pure nothrow
{
super(msg, file, line, next);
}
}
/**
* If the source type $(D_PARAM From) and the target type $(D_PARAM To) are
* equal, does nothing. If $(D_PARAM From) can be implicitly converted to
* $(D_PARAM To), just returns $(D_PARAM from).
*
* Params:
* To = Target type.
*
* Returns: $(D_PARAM from).
*/
deprecated("Use tanya.conv.to instead")
template to(To)
{
/**
* Params:
* From = Source type.
* from = Source value.
*/
ref To to(From)(ref From from)
if (is(To == From))
{
return from;
}
/// ditto
To to(From)(From from)
if (is(Unqual!To == Unqual!From) || (isNumeric!From && isFloatingPoint!To))
{
return from;
}
}
/**
* Performs checked conversion from an integral type $(D_PARAM From) to an
* integral type $(D_PARAM To).
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_PARAM from) converted to $(D_PARAM To).
*
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) is too small or too
* large to be represented by $(D_PARAM To).
*/
deprecated("Use tanya.conv.to instead")
To to(To, From)(From from)
if (isIntegral!From
&& isIntegral!To
&& !is(Unqual!To == Unqual!From)
&& !is(To == enum))
{
static if ((isUnsigned!From && isSigned!To && From.sizeof == To.sizeof)
|| From.sizeof > To.sizeof)
{
if (from > To.max)
{
throw make!ConvException(defaultAllocator,
"Positive integer overflow");
}
}
static if (isSigned!From)
{
static if (isUnsigned!To)
{
if (from < 0)
{
throw make!ConvException(defaultAllocator,
"Negative integer overflow");
}
}
else static if (From.sizeof > To.sizeof)
{
if (from < To.min)
{
throw make!ConvException(defaultAllocator,
"Negative integer overflow");
}
}
}
static if (From.sizeof <= To.sizeof)
{
return from;
}
else static if (isSigned!To)
{
return cast(To) from;
}
else
{
return from & To.max;
}
}
/**
* Converts $(D_PARAM from) to a boolean.
*
* If $(D_PARAM From) is a numeric type, then `1` becomes $(D_KEYWORD true),
* `0` $(D_KEYWORD false). Otherwise $(D_PSYMBOL ConvException) is thrown.
*
* If $(D_PARAM To) is a string (built-in string or $(D_PSYMBOL String)),
* then `"true"` or `"false"` are converted to the appropriate boolean value.
* Otherwise $(D_PSYMBOL ConvException) is thrown.
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_KEYWORD from) converted to a boolean.
*
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) isn't convertible.
*/
deprecated("Use tanya.conv.to instead")
To to(To, From)(From from)
if (isNumeric!From && is(Unqual!To == bool) && !is(Unqual!To == Unqual!From))
{
if (from == 0)
{
return false;
}
else if (from < 0)
{
throw make!ConvException(defaultAllocator,
"Negative number overflow");
}
else if (from <= 1)
{
return true;
}
throw make!ConvException(defaultAllocator,
"Positive number overflow");
}
/// ditto
deprecated("Use tanya.conv.to instead")
To to(To, From)(auto ref const From from)
if ((is(From == String) || isSomeString!From) && is(Unqual!To == bool))
{
if (from == "true")
{
return true;
}
else if (from == "false")
{
return false;
}
throw make!ConvException(defaultAllocator,
"String doesn't contain a boolean value");
}
/**
* Converts a boolean to $(D_PARAM To).
*
* If $(D_PARAM To) is a numeric type, then $(D_KEYWORD true) becomes `1`,
* $(D_KEYWORD false) `0`.
*
* If $(D_PARAM To) is a $(D_PSYMBOL String), then `"true"` or `"false"`
* is returned.
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_PARAM from) converted to $(D_PARAM To).
*/
deprecated("Use tanya.conv.to instead")
To to(To, From)(const From from)
if (is(Unqual!From == bool) && isNumeric!To && !is(Unqual!To == Unqual!From))
{
return from;
}
/// ditto
deprecated("Use tanya.conv.to instead")
To to(To, From)(const From from)
if (is(Unqual!From == bool) && is(Unqual!To == String))
{
return String(from ? "true" : "false");
}
/**
* Converts a floating point number to an integral type.
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: Truncated $(D_PARAM from) (everything after the decimal point is
* dropped).
*
* Throws: $(D_PSYMBOL ConvException) if
* $(D_INLINECODE from < To.min || from > To.max).
*/
deprecated("Use tanya.conv.to instead")
To to(To, From)(From from)
if (isFloatingPoint!From
&& isIntegral!To
&& !is(Unqual!To == Unqual!From)
&& !is(To == enum))
{
if (from > To.max)
{
throw make!ConvException(defaultAllocator,
"Positive number overflow");
}
else if (from < To.min)
{
throw make!ConvException(defaultAllocator,
"Negative number overflow");
}
return cast(To) from;
}
/**
* Performs checked conversion from an integral type $(D_PARAM From) to an
* $(D_KEYWORD enum).
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_KEYWORD enum) value.
*
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) is not a member of
* $(D_PSYMBOL To).
*/
deprecated("Use tanya.conv.to instead")
To to(To, From)(From from)
if (isIntegral!From && is(To == enum))
{
foreach (m; EnumMembers!To)
{
if (from == m)
{
return m;
}
}
throw make!ConvException(defaultAllocator,
"Value not found in enum '" ~ To.stringof ~ "'");
}

View File

@ -0,0 +1,891 @@
/* 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 formatting and conversion functions.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/format/package.d,
* tanya/format/package.d)
*/
module tanya.format;
import tanya.container.string;
import tanya.encoding.ascii;
public import tanya.format.conv;
import tanya.math;
import tanya.memory.op;
import tanya.meta.metafunction;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range.array;
import tanya.range.primitive;
// Integer and floating point to string conversion is based on stb_sprintf
// written by Jeff Roberts.
// Returns the last part of buffer with converted number.
package(tanya) char[] integral2String(T)(T number, return ref char[21] buffer)
@trusted
if (isIntegral!T)
{
// abs the integer.
ulong n64 = number < 0 ? -cast(long) number : number;
char* start = buffer[].ptr + buffer.sizeof - 1;
while (true)
{
// Do in 32-bit chunks (avoid lots of 64-bit divides even with constant
// denominators).
char* o = start - 8;
uint n;
if (n64 >= 100000000)
{
n = n64 % 100000000;
n64 /= 100000000;
}
else
{
n = cast(uint) n64;
n64 = 0;
}
while (n)
{
*--start = cast(char) (n % 10) + '0';
n /= 10;
}
// Ignore the leading zero if it was the last part of the integer.
if (n64 == 0)
{
if ((start[0] == '0')
&& (start != (buffer[].ptr + buffer.sizeof -1)))
{
++start;
}
break;
}
// Copy leading zeros if it wasn't the most significant part of the
// integer.
while (start != o)
{
*--start = '0';
}
}
// Get the length that we have copied.
uint l = cast(uint) ((buffer[].ptr + buffer.sizeof - 1) - start);
if (l == 0)
{
*--start = '0';
l = 1;
}
else if (number < 0) // Set the sign.
{
*--start = '-';
++l;
}
return buffer[$ - l - 1 .. $ - 1];
}
// Converting an integer to string.
@nogc nothrow pure @system unittest
{
char[21] buf;
assert(integral2String(80, buf) == "80");
assert(integral2String(-80, buf) == "-80");
assert(integral2String(0, buf) == "0");
assert(integral2String(uint.max, buf) == "4294967295");
assert(integral2String(int.min, buf) == "-2147483648");
}
/*
* Double-double high-precision floating point number.
*
* The first element is a base value corresponding to the nearest approximation
* of the target $(D_PSYMBOL HP) value, and the second element is an offset
* value corresponding to the difference between the target value and the base.
* Thus, the $(D_PSYMBOL HP) value represented is the sum of the base and the
* offset.
*/
private struct HP
{
private double base;
private double offset = 0.0;
private void normalize() @nogc nothrow pure @safe
{
const double target = this.base + this.offset;
this.offset -= target - this.base;
this.base = target;
}
private void multiply(ref const HP x, ref const HP y)
@nogc nothrow pure @safe
{
HP a, b;
long bt;
this.base = x.base * y.base;
copyFp(x.base, bt);
bt &= ulong.max << 27;
copyFp(bt, a.base);
a.offset = x.base - a.base;
copyFp(y.base, bt);
bt &= ulong.max << 27;
copyFp(bt, b.base);
b.offset = y.base - b.base;
this.offset = a.base * b.base - this.base
+ a.base * b.offset
+ a.offset * b.base
+ a.offset * b.offset;
this.offset += x.base * y.offset + x.offset * y.base;
}
}
private enum special = 0x7000;
private enum char period = '.';
private static const ulong[20] powersOf10 = [
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
10000000000UL,
100000000000UL,
1000000000000UL,
10000000000000UL,
100000000000000UL,
1000000000000000UL,
10000000000000000UL,
100000000000000000UL,
1000000000000000000UL,
10000000000000000000UL,
];
private static const char[201] digitPairs =
"0001020304050607080910111213141516171819202122232425262728293031323334353"
~ "6373839404142434445464748495051525354555657585960616263646566676869707172"
~ "737475767778798081828384858687888990919293949596979899";
private static const HP[23] bottom = [
HP(1e+000), HP(1e+001), HP(1e+002), HP(1e+003), HP(1e+004), HP(1e+005),
HP(1e+006), HP(1e+007), HP(1e+008), HP(1e+009), HP(1e+010), HP(1e+011),
HP(1e+012), HP(1e+013), HP(1e+014), HP(1e+015), HP(1e+016), HP(1e+017),
HP(1e+018), HP(1e+019), HP(1e+020), HP(1e+021), HP(1e+022),
];
private static const HP[22] negativeBottom = [
HP(1e-001, -5.551115123125783e-018),
HP(1e-002, -2.0816681711721684e-019),
HP(1e-003, -2.0816681711721686e-020),
HP(1e-004, -4.7921736023859299e-021),
HP(1e-005, -8.1803053914031305e-022),
HP(1e-006, 4.5251888174113741e-023),
HP(1e-007, 4.5251888174113739e-024),
HP(1e-008, -2.0922560830128471e-025),
HP(1e-009, -6.2281591457779853e-026),
HP(1e-010, -3.6432197315497743e-027),
HP(1e-011, 6.0503030718060191e-028),
HP(1e-012, 2.0113352370744385e-029),
HP(1e-013, -3.0373745563400371e-030),
HP(1e-014, 1.1806906454401013e-032),
HP(1e-015, -7.7705399876661076e-032),
HP(1e-016, 2.0902213275965398e-033),
HP(1e-017, -7.1542424054621921e-034),
HP(1e-018, -7.1542424054621926e-035),
HP(1e-019, 2.4754073164739869e-036),
HP(1e-020, 5.4846728545790429e-037),
HP(1e-021, 9.2462547772103625e-038),
HP(1e-022, -4.8596774326570872e-039),
];
private static const HP[13] top = [
HP(1e+023, 8388608),
HP(1e+046, 6.8601809640529717e+028),
HP(1e+069, -7.253143638152921e+052),
HP(1e+092, -4.3377296974619174e+075),
HP(1e+115, -1.5559416129466825e+098),
HP(1e+138, -3.2841562489204913e+121),
HP(1e+161, -3.7745893248228135e+144),
HP(1e+184, -1.7356668416969134e+167),
HP(1e+207, -3.8893577551088374e+190),
HP(1e+230, -9.9566444326005119e+213),
HP(1e+253, 6.3641293062232429e+236),
HP(1e+276, -5.2069140800249813e+259),
HP(1e+299, -5.2504760255204387e+282),
];
private static const HP[13] negativeTop = [
HP(1e-023, 3.9565301985100693e-040L),
HP(1e-046, -2.299904345391321e-063L),
HP(1e-069, 3.6506201437945798e-086L),
HP(1e-092, 1.1875228833981544e-109L),
HP(1e-115, -5.0644902316928607e-132L),
HP(1e-138, -6.7156837247865426e-155L),
HP(1e-161, -2.812077463003139e-178L),
HP(1e-184, -5.7778912386589953e-201L),
HP(1e-207, 7.4997100559334532e-224L),
HP(1e-230, -4.6439668915134491e-247L),
HP(1e-253, -6.3691100762962136e-270L),
HP(1e-276, -9.436808465446358e-293L),
HP(1e-299, 8.0970921678014997e-317L),
];
private enum ulong tenTo19th = 1000000000000000000UL;
// Power can be -323 to +350.
private HP raise2Power10(const HP value, int power)
@nogc nothrow pure @safe
{
HP result;
if ((power >= 0) && (power <= 22))
{
result.multiply(value, bottom[power]);
}
else
{
HP p2;
int e = power;
if (power < 0)
{
e = -e;
}
int et = (e * 0x2c9) >> 14; // % 23
if (et > 13)
{
et = 13;
}
int eb = e - (et * 23);
result = value;
if (power < 0)
{
if (eb != 0)
{
--eb;
result.multiply(value, negativeBottom[eb]);
}
if (et)
{
result.normalize();
--et;
p2.multiply(result, negativeTop[et]);
result = p2;
}
}
else
{
if (eb != 0)
{
e = eb;
if (eb > 22)
{
eb = 22;
}
e -= eb;
result.multiply(value, bottom[eb]);
if (e)
{
result.normalize();
p2.multiply(result, bottom[e]);
result = p2;
}
}
if (et != 0)
{
result.normalize();
--et;
p2.multiply(result, top[et]);
result = p2;
}
}
}
result.normalize();
return result;
}
/*
* Given a float value, returns the significant bits in bits, and the position
* of the decimal point in $(D_PARAM exponent). +/-Inf and NaN are specified
* by special values returned in the $(D_PARAM exponent). Sing bit is set in
* $(D_PARAM sign).
*/
private const(char)[] real2String(double value,
ref char[512] buffer,
out int exponent,
out bool sign) @nogc nothrow pure @trusted
{
long bits;
copyFp(value, bits);
exponent = (bits >> 52) & 0x7ff;
sign = signBit(value);
if (sign)
{
value = -value;
}
if (exponent == 2047) // Is NaN or Inf?
{
exponent = special;
return (bits & ((1UL << 52) - 1)) != 0 ? "NaN" : "Inf";
}
if (exponent == 0) // Is zero or denormal?
{
if ((bits << 1) == 0) // Zero.
{
exponent = 1;
buffer[0] = '0';
return buffer[0 .. 1];
}
// Find the right exponent for denormals.
for (long cursor = 1UL << 51; (bits & cursor) == 0; cursor >>= 1)
{
--exponent;
}
}
// "617 / 2048" and "1233 / 4096" are estimations for the common logarithm
// (log10) of 2. Multiplied by a binary number it tells how big the number
// is in decimals, so it translates the binary exponent into decimal
// format. The estimation is tweaked to hit or undershoot by no more than
// 1 of log10 of all exponents 1..2046.
int tens = exponent - 1023; // Bias.
if (tens < 0)
{
tens = tens * 617 / 2048;
}
else
{
tens = tens * 1233 / 4096 + 1;
}
// Move the significant bits into position and stick them into an int.
HP p = raise2Power10(HP(value), 18 - tens);
// Get full as much precision from double-double as possible.
bits = cast(long) p.base;
double vh = cast(double) bits;
auto a = HP(p.base - vh);
double t = a.base - p.base;
a.offset = p.base - a.base + t - vh - t;
bits += cast(long) (a.base + a.offset + p.offset);
// Check if we undershot (bits >= 10 ^ 19).
if ((cast(ulong) bits) >= 1000000000000000000UL)
{
++tens;
}
// Now do the rounding in integer land.
enum uint fracDigits = 6;
uint dg = 1;
if ((cast(ulong) bits) >= powersOf10[9])
{
dg = 10;
}
uint length;
while ((cast(ulong) bits) >= powersOf10[dg])
{
++dg;
if (dg == 20)
{
goto NoRound;
}
}
if (fracDigits < dg)
{
// Add 0.5 at the right position and round.
length = dg - fracDigits;
if (length >= 24)
{
goto NoRound;
}
ulong r = powersOf10[length];
bits = bits + (r / 2);
if ((cast(ulong) bits) >= powersOf10[dg])
{
++tens;
}
bits /= r;
}
NoRound:
// Kill long trailing runs of zeros.
if (bits)
{
while (bits > 0xffffffff)
{
if (bits % 1000)
{
goto Zeroed;
}
bits /= 1000;
}
auto n = cast(uint) bits;
while ((n % 1000) == 0)
{
n /= 1000;
}
bits = n;
}
Zeroed:
// Convert to string.
auto result = buffer.ptr + 64;
length = 0;
while (true)
{
uint n;
char* o = result - 8;
// Do the conversion in chunks of U32s (avoid most 64-bit divides,
// worth it, constant denomiators be damned).
if (bits >= 100000000)
{
n = cast(uint) (bits % 100000000);
bits /= 100000000;
}
else
{
n = cast(uint) bits;
bits = 0;
}
while (n)
{
result -= 2;
*cast(ushort*) result = *cast(ushort*) &digitPairs[(n % 100) * 2];
n /= 100;
length += 2;
}
if (bits == 0)
{
if ((length != 0) && (result[0] == '0'))
{
++result;
--length;
}
break;
}
for (; result !is o; ++length, --result)
{
*result = '0';
}
}
exponent = tens;
return result[0 .. length];
}
/*
* Copies double into long and back bitwise.
*/
private void copyFp(T, U)(ref const U src, ref T dest) @trusted
if (T.sizeof == U.sizeof)
{
copy((&src)[0 .. 1], (&dest)[0 .. 1]);
}
package(tanya) String format(string fmt, Args...)(auto ref Args args)
{
String result;
static if (is(Unqual!(Args[0]) == typeof(null)))
{
result.insertBack("null");
}
else static if(is(Unqual!(Args[0]) == bool))
{
result.insertBack(args[0] ? "true" : "false");
}
else static if (isSomeString!(Args[0])) // String
{
if (args[0] is null)
{
result.insertBack("null");
}
else
{
result.insertBack(args[0]);
}
}
else static if (isSomeChar!(Args[0])) // Char
{
result.insertBack(args[0]);
}
else static if (isFloatingPoint!(Args[0])) // Float
{
char[512] buffer; // Big enough for e+308 or e-307.
char[8] tail = 0;
char[] bufferSlice = buffer[64 .. $];
uint precision = 6;
bool negative;
int decimalPoint;
// Read the double into a string.
auto realString = real2String(args[0], buffer, decimalPoint, negative);
auto length = cast(uint) realString.length;
// Clamp the precision and delete extra zeros after clamp.
uint n = precision;
if (length > precision)
{
length = precision;
}
while ((length > 1)
&& (precision != 0)
&& (realString[length - 1] == '0'))
{
--precision;
--length;
}
if (negative)
{
result.insertBack('-');
}
if (decimalPoint == special)
{
result.insertBack(realString);
goto ParamEnd;
}
// Should we use sceintific notation?
if ((decimalPoint <= -4) || (decimalPoint > cast(int) n))
{
if (precision > length)
{
precision = length - 1;
}
else if (precision > 0)
{
// When using scientific notation, there is one digit before the
// decimal.
--precision;
}
// Handle leading chars.
bufferSlice.front = realString[0];
bufferSlice.popFront();
if (precision != 0)
{
bufferSlice.front = period;
bufferSlice.popFront();
}
// Handle after decimal.
if ((length - 1) > precision)
{
length = precision + 1;
}
realString[1 .. length].copy(bufferSlice);
bufferSlice.popFrontExactly(length - 1);
// Dump the exponent.
tail[1] = 'e';
--decimalPoint;
if (decimalPoint < 0)
{
tail[2] = '-';
decimalPoint = -decimalPoint;
}
else
{
tail[2] = '+';
}
n = decimalPoint >= 100 ? 5 : 4;
tail[0] = cast(char) n;
while (true)
{
tail[n] = '0' + decimalPoint % 10;
if (n <= 3)
{
break;
}
--n;
decimalPoint /= 10;
}
}
else
{
if (decimalPoint > 0)
{
precision = decimalPoint < (cast(int) length)
? length - decimalPoint
: 0;
}
else
{
precision = -decimalPoint
+ (precision > length ? length : precision);
}
// Handle the three decimal varieties.
if (decimalPoint <= 0)
{
// Handle 0.000*000xxxx.
bufferSlice.front = '0';
bufferSlice.popFront();
if (precision != 0)
{
bufferSlice.front = period;
bufferSlice.popFront();
}
n = -decimalPoint;
if (n > precision)
{
n = precision;
}
fill!'0'(bufferSlice[0 .. n]);
bufferSlice.popFrontExactly(n);
if ((length + n) > precision)
{
length = precision - n;
}
realString[0 .. length].copy(bufferSlice);
bufferSlice.popFrontExactly(length);
}
else if (cast(uint) decimalPoint >= length)
{
// Handle xxxx000*000.0.
n = 0;
do
{
bufferSlice.front = realString[n];
bufferSlice.popFront();
++n;
}
while (n < length);
if (n < cast(uint) decimalPoint)
{
n = decimalPoint - n;
fill!'0'(bufferSlice[0 .. n]);
bufferSlice.popFrontExactly(n);
}
if (precision != 0)
{
bufferSlice.front = period;
bufferSlice.popFront();
}
}
else
{
// Handle xxxxx.xxxx000*000.
n = 0;
do
{
bufferSlice.front = realString[n];
bufferSlice.popFront();
++n;
}
while (n < cast(uint) decimalPoint);
if (precision > 0)
{
bufferSlice.front = period;
bufferSlice.popFront();
}
if ((length - decimalPoint) > precision)
{
length = precision + decimalPoint;
}
realString[n .. length].copy(bufferSlice);
bufferSlice.popFrontExactly(length - n);
}
}
// Get the length that we've copied.
length = cast(uint) (buffer.length - bufferSlice.length);
result.insertBack(buffer[64 .. length]); // Number.
result.insertBack(tail[1 .. tail[0] + 1]); // Tail.
}
else static if (isPointer!(Args[0])) // Pointer
{
char[size_t.sizeof * 2] buffer;
size_t position = buffer.length;
auto address = cast(size_t) args[0];
do // Write at least "0" if the pointer is null.
{
buffer[--position] = lowerHexDigits[cast(size_t) (address & 15)];
address >>= 4;
}
while (address != 0);
result.insertBack("0x");
result.insertBack(buffer[position .. $]);
}
else static if (isIntegral!(Args[0])) // Integer
{
char[21] buffer;
result.insertBack(integral2String(args[0], buffer));
}
else
{
static assert(false);
}
ParamEnd:
return result;
}
// One argument tests.
@nogc pure @safe unittest
{
// Modifiers.
assert(format!"{}"(8.5) == "8.5");
assert(format!"{}"(8.6) == "8.6");
assert(format!"{}"(1000) == "1000");
assert(format!"{}"(1) == "1");
assert(format!"{}"(10.25) == "10.25");
assert(format!"{}"(1) == "1");
assert(format!"{}"(0.01) == "0.01");
// String printing.
assert(format!"{}"("Some weired string") == "Some weired string");
assert(format!"{}"(cast(string) null) == "null");
assert(format!"{}"('c') == "c");
// Integer.
assert(format!"{}"(8) == "8");
assert(format!"{}"(8) == "8");
assert(format!"{}"(-8) == "-8");
assert(format!"{}"(-8L) == "-8");
assert(format!"{}"(8) == "8");
assert(format!"{}"(100000001) == "100000001");
assert(format!"{}"(99999999L) == "99999999");
assert(format!"{}"(10) == "10");
assert(format!"{}"(10L) == "10");
// Floating point.
assert(format!"{}"(0.1234) == "0.1234");
assert(format!"{}"(0.3) == "0.3");
assert(format!"{}"(0.333333333333) == "0.333333");
assert(format!"{}"(38234.1234) == "38234.1");
assert(format!"{}"(-0.3) == "-0.3");
assert(format!"{}"(0.000000000000000006) == "6e-18");
assert(format!"{}"(0.0) == "0");
assert(format!"{}"(double.init) == "NaN");
assert(format!"{}"(-double.init) == "-NaN");
assert(format!"{}"(double.infinity) == "Inf");
assert(format!"{}"(-double.infinity) == "-Inf");
assert(format!"{}"(0.000000000000000000000000003) == "3e-27");
assert(format!"{}"(0.23432e304) == "2.3432e+303");
assert(format!"{}"(-0.23432e8) == "-2.3432e+07");
assert(format!"{}"(1e-307) == "1e-307");
assert(format!"{}"(1e+8) == "1e+08");
assert(format!"{}"(111234.1) == "111234");
assert(format!"{}"(0.999) == "0.999");
assert(format!"{}"(0x1p-16382L) == "0");
assert(format!"{}"(1e+3) == "1000");
assert(format!"{}"(38234.1234) == "38234.1");
// typeof(null).
assert(format!"{}"(null) == "null");
// Boolean.
assert(format!"{}"(true) == "true");
assert(format!"{}"(false) == "false");
}
// Unsafe tests with pointers.
@nogc pure @system unittest
{
// Pointer convesions
assert(format!"{}"(cast(void*) 1) == "0x1");
assert(format!"{}"(cast(void*) 20) == "0x14");
assert(format!"{}"(cast(void*) null) == "0x0");
}
private struct FormatSpec
{
}
// Returns the position of `tag` in `fmt`. If `tag` can't be found, returns the
// length of `fmt`.
private size_t specPosition(string fmt, char tag)()
{
foreach (i, c; fmt)
{
if (c == tag)
{
return i;
}
}
return fmt.length;
}
private template ParseFmt(string fmt, size_t pos = 0)
{
static if (fmt.length == 0)
{
alias ParseFmt = AliasSeq!();
}
else static if (fmt[0] == '{')
{
static if (fmt.length > 1 && fmt[1] == '{')
{
enum size_t pos = specPosition!(fmt[2 .. $], '{') + 2;
alias ParseFmt = AliasSeq!(fmt[1 .. pos],
ParseFmt!(fmt[pos .. $], pos));
}
else
{
enum size_t pos = specPosition!(fmt[1 .. $], '}') + 1;
static if (pos < fmt.length)
{
alias ParseFmt = AliasSeq!(FormatSpec(),
ParseFmt!(fmt[pos + 1 .. $], pos + 1));
}
else
{
static assert(false, "Enclosing '}' is missing");
}
}
}
else
{
enum size_t pos = specPosition!(fmt, '{');
alias ParseFmt = AliasSeq!(fmt[0 .. pos],
ParseFmt!(fmt[pos .. $], pos));
}
}
@nogc nothrow pure @safe unittest
{
static assert(ParseFmt!"".length == 0);
static assert(ParseFmt!"asdf".length == 1);
static assert(ParseFmt!"asdf"[0] == "asdf");
static assert(ParseFmt!"{}".length == 1);
}

View File

@ -1,112 +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.container.math;
version (unittest)
{
import std.algorithm.iteration;
}
@nogc:
/**
* Computes $(D_PARAM x) to the power $(D_PARAM y) modulo $(D_PARAM z).
*
* Params:
* x = Base.
* y = Exponent.
* z = Divisor.
*
* Returns: Reminder of the division of $(D_PARAM x) to the power $(D_PARAM y)
* by $(D_PARAM z).
*/
ulong pow(ulong x, ulong y, ulong z) @safe nothrow pure
in
{
assert(z > 0);
}
out (result)
{
assert(result >= 0);
}
body
{
ulong mask = ulong.max / 2 + 1, result;
if (y == 0)
{
return 1 % z;
}
else if (y == 1)
{
return x % z;
}
do
{
auto bit = y & mask;
if (!result && bit)
{
result = x;
continue;
}
result *= result;
if (bit)
{
result *= x;
}
result %= z;
}
while (mask >>= 1);
return result;
}
///
unittest
{
assert(pow(3, 5, 7) == 5);
assert(pow(2, 2, 1) == 0);
assert(pow(3, 3, 3) == 0);
assert(pow(7, 4, 2) == 1);
assert(pow(53, 0, 2) == 1);
assert(pow(53, 1, 3) == 2);
assert(pow(53, 2, 5) == 4);
assert(pow(0, 0, 5) == 1);
assert(pow(0, 5, 5) == 0);
}
/**
* Checks if $(D_PARAM x) is a prime.
*
* Params:
* x = The number should be checked.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is a prime number,
* $(D_KEYWORD false) otherwise.
*/
bool isPseudoprime(ulong x) @safe nothrow pure
{
return pow(2, x - 1, x) == 1;
}
///
unittest
{
uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719,
74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821,
74827, 9973, 104729, 15485867, 49979693, 104395303,
593441861, 104729, 15485867, 49979693, 104395303,
593441861, 899809363, 982451653];
known.each!((ref x) => assert(isPseudoprime(x)));
}

1574
source/tanya/math/mp.d Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,165 @@
/* 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.
* 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;
version (TanyaNative)
{
}
else
{
import core.math : fabs;
import std.math : log;
}
/**
* Calculates the absolute value of a number.
*
* Params:
* I = Value type.
* x = Value.
*
* Returns: Absolute value of $(D_PARAM x).
*/
I abs(I)(I x)
if (isIntegral!I)
{
static if (isSigned!I)
{
return x >= 0 ? x : -x;
}
else
{
return x;
}
}
///
pure nothrow @safe @nogc 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));
}
version (D_Ddoc)
{
/// ditto
I abs(I)(I x)
if (isFloatingPoint!I);
}
else version (TanyaNative)
{
extern I abs(I)(I number) pure nothrow @safe @nogc
if (isFloatingPoint!I);
}
else
{
I abs(I)(I x)
if (isFloatingPoint!I)
{
return fabs(cast(real) x);
}
}
///
pure nothrow @safe @nogc 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
I abs(I : Integer)(const auto ref I x)
{
auto result = Integer(x, x.allocator);
result.sign = Sign.positive;
return result;
}
/// ditto
I abs(I : Integer)(I x)
{
x.sign = Sign.positive;
return x;
}
version (D_Ddoc)
{
/**
* Calculates natural logarithm of $(D_PARAM x).
*
* Params:
* x = Argument.
*
* Returns: Natural logarithm of $(D_PARAM x).
*/
float ln(float x) pure nothrow @safe @nogc;
/// ditto
double ln(double x) pure nothrow @safe @nogc;
/// ditto
real ln(real x) pure nothrow @safe @nogc;
}
else version (TanyaNative)
{
extern float ln(float x) pure nothrow @safe @nogc;
extern double ln(double x) pure nothrow @safe @nogc;
extern real ln(real x) pure nothrow @safe @nogc;
}
else
{
float ln(float x) pure nothrow @safe @nogc
{
return log(x);
}
double ln(double x) pure nothrow @safe @nogc
{
return log(x);
}
alias ln = log;
}
///
pure nothrow @safe @nogc 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);
}

897
source/tanya/math/package.d Normal file
View File

@ -0,0 +1,897 @@
/* 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 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-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/math/package.d,
* tanya/math/package.d)
*/
module tanya.math;
import tanya.algorithm.mutation;
import tanya.math.mp;
import tanya.math.nbtheory;
import tanya.meta.trait;
import tanya.meta.transform;
/// Floating-point number precisions according to IEEE-754.
enum IEEEPrecision : ubyte
{
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_);
}
private union FloatBits(F)
{
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));
}
/**
* Computes $(D_PARAM x) to the power $(D_PARAM y) modulo $(D_PARAM z).
*
* If $(D_PARAM I) is an $(D_PSYMBOL Integer), the allocator of $(D_PARAM x)
* is used to allocate the result.
*
* Params:
* I = Base type.
* G = Exponent type.
* H = Divisor type:
* x = Base.
* y = Exponent.
* z = Divisor.
*
* Returns: Reminder of the division of $(D_PARAM x) to the power $(D_PARAM y)
* by $(D_PARAM z).
*
* 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)
if (isIntegral!I && isIntegral!G && isIntegral!H)
in
{
assert(z > 0, "Division by zero.");
}
body
{
G mask = G.max / 2 + 1;
H result;
if (y == 0)
{
return 1 % z;
}
else if (y == 1)
{
return x % z;
}
do
{
immutable bit = y & mask;
if (!result && bit)
{
result = x;
continue;
}
result *= result;
if (bit)
{
result *= x;
}
result %= z;
}
while (mask >>= 1);
return result;
}
/// ditto
I pow(I)(const auto ref I x, const auto ref I y, const auto ref I z)
if (is(I == Integer))
in
{
assert(z.length > 0, "Division by zero.");
}
body
{
size_t i;
auto tmp1 = Integer(x, x.allocator);
auto result = Integer(x.allocator);
bool firstBit;
if (x.size == 0 && y.size != 0)
{
i = y.size;
}
else
{
result = 1;
}
while (i < y.size)
{
for (uint mask = 0x01; mask != 0x10000000; mask <<= 1)
{
if (y.rep[i] & mask)
{
result *= tmp1;
result %= z;
}
auto tmp2 = tmp1;
tmp1 *= tmp2;
tmp1 %= z;
}
++i;
}
return result;
}
///
@nogc nothrow pure @safe unittest
{
assert(pow(3, 5, 7) == 5);
assert(pow(2, 2, 1) == 0);
assert(pow(3, 3, 3) == 0);
assert(pow(7, 4, 2) == 1);
assert(pow(53, 0, 2) == 1);
assert(pow(53, 1, 3) == 2);
assert(pow(53, 2, 5) == 4);
assert(pow(0, 0, 5) == 1);
assert(pow(0, 5, 5) == 0);
}
///
@nogc nothrow pure @safe unittest
{
assert(pow(Integer(3), Integer(5), Integer(7)) == 5);
assert(pow(Integer(2), Integer(2), Integer(1)) == 0);
assert(pow(Integer(3), Integer(3), Integer(3)) == 0);
assert(pow(Integer(7), Integer(4), Integer(2)) == 1);
assert(pow(Integer(53), Integer(0), Integer(2)) == 1);
assert(pow(Integer(53), Integer(1), Integer(3)) == 2);
assert(pow(Integer(53), Integer(2), Integer(5)) == 4);
assert(pow(Integer(0), Integer(0), Integer(5)) == 1);
assert(pow(Integer(0), Integer(5), Integer(5)) == 0);
}
/**
* Checks if $(D_PARAM x) is a prime.
*
* Params:
* x = The number should be checked.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is a prime number,
* $(D_KEYWORD false) otherwise.
*/
bool isPseudoprime(ulong x) @nogc nothrow pure @safe
{
return pow(2, x - 1, x) == 1;
}
///
@nogc nothrow pure @safe unittest
{
assert(74623.isPseudoprime);
assert(104729.isPseudoprime);
assert(15485867.isPseudoprime);
assert(!15485868.isPseudoprime);
}
@nogc nothrow pure @safe unittest
{
assert(74653.isPseudoprime);
assert(74687.isPseudoprime);
assert(74699.isPseudoprime);
assert(74707.isPseudoprime);
assert(74713.isPseudoprime);
assert(74717.isPseudoprime);
assert(74719.isPseudoprime);
assert(74747.isPseudoprime);
assert(74759.isPseudoprime);
assert(74761.isPseudoprime);
assert(74771.isPseudoprime);
assert(74779.isPseudoprime);
assert(74797.isPseudoprime);
assert(74821.isPseudoprime);
assert(74827.isPseudoprime);
assert(9973.isPseudoprime);
assert(49979693.isPseudoprime);
assert(104395303.isPseudoprime);
assert(593441861.isPseudoprime);
assert(104729.isPseudoprime);
assert(15485867.isPseudoprime);
assert(49979693.isPseudoprime);
assert(104395303.isPseudoprime);
assert(593441861.isPseudoprime);
assert(899809363.isPseudoprime);
assert(982451653.isPseudoprime);
}
/**
* Determines minimum of two numbers.
*
* Params:
* x = First number.
* y = Second number.
*
* Returns: $(D_PARAM x) if $(D_PARAM x) is smaller than $(D_PSYMBOL y),
* $(D_PARAM y) otherwise.
*
* See_Also: $(D_PSYMBOL max).
*/
T min(T)(T x, T y)
if (isIntegral!T)
{
return x < y ? x : y;
}
///
@nogc nothrow pure @safe unittest
{
assert(min(5, 3) == 3);
assert(min(4, 4) == 4);
}
/// ditto
T min(T)(T x, T y)
if (isFloatingPoint!T)
{
if (isNaN(x))
{
return y;
}
if (isNaN(y))
{
return x;
}
return x < y ? x : y;
}
///
@nogc nothrow pure @safe unittest
{
assert(min(5.2, 3.0) == 3.0);
assert(min(5.2, double.nan) == 5.2);
assert(min(double.nan, 3.0) == 3.0);
assert(isNaN(min(double.nan, double.nan)));
}
/// ditto
ref T min(T)(ref T x, ref T y)
if (is(Unqual!T == Integer))
{
return x < y ? x : y;
}
/// ditto
T min(T)(T x, T y)
if (is(T == Integer))
{
return x < y ? move(x) : move(y);
}
///
@nogc nothrow pure @safe unittest
{
assert(min(Integer(5), Integer(3)) == 3);
}
/**
* Determines maximum of two numbers.
*
* Params:
* x = First number.
* y = Second number.
*
* Returns: $(D_PARAM x) if $(D_PARAM x) is larger than $(D_PSYMBOL y),
* $(D_PARAM y) otherwise.
*
* See_Also: $(D_PSYMBOL min).
*/
T max(T)(T x, T y)
if (isIntegral!T)
{
return x > y ? x : y;
}
///
@nogc nothrow pure @safe unittest
{
assert(max(5, 3) == 5);
assert(max(4, 4) == 4);
}
/// ditto
T max(T)(T x, T y)
if (isFloatingPoint!T)
{
if (isNaN(x))
{
return y;
}
if (isNaN(y))
{
return x;
}
return x > y ? x : y;
}
///
@nogc nothrow pure @safe unittest
{
assert(max(5.2, 3.0) == 5.2);
assert(max(5.2, double.nan) == 5.2);
assert(max(double.nan, 3.0) == 3.0);
assert(isNaN(max(double.nan, double.nan)));
}
/// ditto
ref T max(T)(ref T x, ref T y)
if (is(Unqual!T == Integer))
{
return x > y ? x : y;
}
/// ditto
T max(T)(T x, T y)
if (is(T == Integer))
{
return x > y ? move(x) : move(y);
}
///
@nogc nothrow pure @safe unittest
{
assert(max(Integer(5), Integer(3)) == 5);
}
// min/max accept const and mutable references.
@nogc nothrow pure @safe unittest
{
{
Integer i1 = 5, i2 = 3;
assert(min(i1, i2) == 3);
assert(max(i1, i2) == 5);
}
{
const Integer i1 = 5, i2 = 3;
assert(min(i1, i2) == 3);
assert(max(i1, i2) == 5);
}
}

336
source/tanya/math/random.d Normal file
View File

@ -0,0 +1,336 @@
/* 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/. */
/**
* Random number generator.
*
* 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)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/math/random.d,
* tanya/math/random.d)
*/
module tanya.math.random;
import std.digest.sha;
import std.typecons;
import tanya.memory;
/// Block size of entropy accumulator (SHA-512).
enum blockSize = 64;
/// Maximum amount gathered from the entropy sources.
enum maxGather = 128;
/**
* Exception thrown if random number generating fails.
*/
class EntropyException : Exception
{
/**
* Params:
* msg = Message to output.
* 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) pure @safe nothrow const @nogc
{
super(msg, file, line, next);
}
}
/**
* Interface for implementing entropy sources.
*/
abstract class EntropySource
{
/// Amount of already generated entropy.
protected ushort size_;
/**
* Returns: Minimum bytes required from the entropy source.
*/
@property ubyte threshold() const pure nothrow @safe @nogc;
/**
* Returns: Whether this entropy source is strong.
*/
@property bool strong() const pure nothrow @safe @nogc;
/**
* Returns: Amount of already generated entropy.
*/
@property ushort size() const pure nothrow @safe @nogc
{
return size_;
}
/**
* Params:
* size = Amount of already generated entropy. Cannot be smaller than the
* already set value.
*/
@property void size(ushort size) pure nothrow @safe @nogc
{
size_ = size;
}
/**
* Poll the entropy source.
*
* Params:
* output = Buffer to save the generate random sequence (the method will
* to fill the buffer).
*
* Returns: Number of bytes that were copied to the $(D_PARAM output)
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
*/
Nullable!ubyte poll(out ubyte[maxGather] output) @nogc;
}
version (linux)
{
extern (C) long syscall(long number, ...) nothrow @system @nogc;
/**
* Uses getrandom system call.
*/
class PlatformEntropySource : EntropySource
{
/**
* Returns: Minimum bytes required from the entropy source.
*/
override @property ubyte threshold() const pure nothrow @safe @nogc
{
return 32;
}
/**
* Returns: Whether this entropy source is strong.
*/
override @property bool strong() const pure nothrow @safe @nogc
{
return true;
}
/**
* Poll the entropy source.
*
* Params:
* output = Buffer to save the generate random sequence (the method will
* to fill the buffer).
*
* Returns: Number of bytes that were copied to the $(D_PARAM output)
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
*/
override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow @nogc
out (length)
{
assert(length <= maxGather);
}
body
{
// 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)
{
ret = cast(ubyte) length;
}
return ret;
}
}
version (X86_64)
{
private unittest
{
auto entropy = defaultAllocator.make!Entropy();
ubyte[blockSize] output;
output = entropy.random;
defaultAllocator.dispose(entropy);
}
}
}
/**
* Pseudorandom number generator.
* ---
* auto entropy = defaultAllocator.make!Entropy();
*
* ubyte[blockSize] output;
*
* output = entropy.random;
*
* defaultAllocator.dispose(entropy);
* ---
*/
class Entropy
{
/// Entropy sources.
protected EntropySource[] sources;
private ubyte sourceCount_;
private shared Allocator allocator;
/// Entropy accumulator.
protected SHA!(maxGather * 8, 512) accumulator;
/**
* Params:
* maxSources = Maximum amount of entropy sources can be set.
* allocator = Allocator to allocate entropy sources available on the
* system.
*/
this(const size_t maxSources = 20,
shared Allocator allocator = defaultAllocator) @nogc
in
{
assert(maxSources > 0 && maxSources <= ubyte.max);
assert(allocator !is null);
}
body
{
allocator.resize(sources, maxSources);
version (linux)
{
this ~= allocator.make!PlatformEntropySource;
}
}
/**
* Returns: Amount of the registered entropy sources.
*/
@property ubyte sourceCount() const pure nothrow @safe @nogc
{
return sourceCount_;
}
/**
* Add an entropy source.
*
* Params:
* source = Entropy source.
*
* Returns: $(D_PSYMBOL this).
*
* See_Also:
* $(D_PSYMBOL EntropySource)
*/
Entropy opOpAssign(string op)(EntropySource source)
pure nothrow @safe @nogc
if (op == "~")
in
{
assert(sourceCount_ <= sources.length);
}
body
{
sources[sourceCount_++] = source;
return this;
}
/**
* Returns: Generated random sequence.
*
* Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was
* registered or it failed.
*/
@property ubyte[blockSize] random() @nogc
in
{
assert(sourceCount_ > 0, "No entropy sources defined.");
}
body
{
bool haveStrong;
ushort done;
ubyte[blockSize] output;
do
{
ubyte[maxGather] buffer;
// Run through our entropy sources
for (ubyte i; i < sourceCount; ++i)
{
auto outputLength = sources[i].poll(buffer);
if (!outputLength.isNull)
{
if (outputLength > 0)
{
update(i, buffer, outputLength);
sources[i].size = cast(ushort) (sources[i].size + outputLength);
}
if (sources[i].size < sources[i].threshold)
{
continue;
}
else if (sources[i].strong)
{
haveStrong = true;
}
}
done = 257;
}
}
while (++done < 256);
if (!haveStrong)
{
throw allocator.make!EntropyException("No strong entropy source defined.");
}
output = accumulator.finish();
// Reset accumulator and counters and recycle existing entropy
accumulator.start();
// Perform second SHA-512 on entropy
output = sha512Of(output);
for (ubyte i; i < sourceCount; ++i)
{
sources[i].size = 0;
}
return output;
}
/**
* Update entropy accumulator.
*
* Params:
* sourceId = Entropy source index in $(D_PSYMBOL sources).
* data = Data got from the entropy source.
* length = Length of the received data.
*/
protected void update(in ubyte sourceId,
ref ubyte[maxGather] data,
ubyte length) pure nothrow @safe @nogc
{
ubyte[2] header;
if (length > blockSize)
{
data[0 .. 64] = sha512Of(data);
length = blockSize;
}
header[0] = sourceId;
header[1] = length;
accumulator.put(header);
accumulator.put(data[0 .. length]);
}
}

View File

@ -3,35 +3,39 @@
* 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-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/allocator.d,
* tanya/memory/allocator.d)
*/
module tanya.memory.allocator;
import std.experimental.allocator;
import std.traits;
version (unittest)
{
import tanya.memory : defaultAllocator;
}
/**
* Allocator interface.
* Abstract class implementing a basic allocator.
*/
interface Allocator
{
/**
* Returns: Alignment offered.
*/
@property uint alignment() const shared pure nothrow @safe @nogc;
/**
* Allocates $(D_PARAM size) bytes of memory.
*
* Params:
* size = Amount of memory to allocate.
*
* Returns: The pointer to the new allocated memory.
* Returns: Pointer to the new allocated memory.
*/
void[] allocate(size_t size) shared;
void[] allocate(const size_t size) shared pure nothrow @nogc;
/**
* Deallocates a memory block.
@ -41,7 +45,7 @@ interface Allocator
*
* Returns: Whether the deallocation was successful.
*/
bool deallocate(void[] p) shared;
bool deallocate(void[] p) shared pure nothrow @nogc;
/**
* Increases or decreases the size of a memory block.
@ -50,58 +54,28 @@ interface Allocator
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: Whether the reallocation was successful.
* Returns: Pointer to the allocated memory.
*/
bool reallocate(ref void[] p, size_t size) shared;
bool reallocate(ref void[] p, const size_t size) shared pure nothrow @nogc;
/**
* Returns: The alignment offered.
*/
@property immutable(uint) alignment() shared const @safe pure nothrow;
}
/**
* Params:
* T = Element type of the array being created.
* allocator = The allocator used for getting memory.
* array = A reference to the array being changed.
* length = New array length.
* Reallocates a memory block in place if possible or returns
* $(D_KEYWORD false). This function cannot be used to allocate or
* deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
* $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
*
* Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could
* not be reallocated. In the latter
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
*/
bool resizeArray(T)(shared Allocator allocator,
ref T[] array,
in size_t length)
{
void[] buf = array;
if (!allocator.reallocate(buf, length * T.sizeof))
{
return false;
}
array = cast(T[]) buf;
return true;
bool reallocateInPlace(ref void[] p, const size_t size)
shared pure nothrow @nogc;
}
///
unittest
package template GetPureInstance(T : Allocator)
{
int[] p;
defaultAllocator.resizeArray(p, 20);
assert(p.length == 20);
defaultAllocator.resizeArray(p, 30);
assert(p.length == 30);
defaultAllocator.resizeArray(p, 10);
assert(p.length == 10);
defaultAllocator.resizeArray(p, 0);
assert(p is null);
alias GetPureInstance = shared(T) function()
pure nothrow @nogc;
}
enum bool isFinalizable(T) = is(T == class) || is(T == interface)
|| hasElaborateDestructor!T || isDynamicArray!T;

View File

@ -3,23 +3,35 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Copyright: Eugene Wissner 2016.
* Allocator based on $(D_PSYMBOL malloc), $(D_PSYMBOL realloc) and $(D_PSYMBOL free).
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/mallocator.d,
* tanya/memory/mallocator.d)
*/
module tanya.memory.mallocator;
import tanya.memory.allocator;
import core.exception;
version (TanyaPhobos):
import core.stdc.stdlib;
import std.algorithm.comparison;
import tanya.memory.allocator;
/**
* Wrapper for malloc/realloc/free from the C standard library.
* Wrapper for $(D_PSYMBOL malloc)/$(D_PSYMBOL realloc)/$(D_PSYMBOL free) from
* the C standard library.
*/
class Mallocator : Allocator
final class Mallocator : Allocator
{
private alias MallocType = extern (C) void* function(size_t)
pure nothrow @system @nogc;
private alias FreeType = extern (C) void function(void*)
pure nothrow @system @nogc;
private alias ReallocType = extern (C) void* function(void*, size_t)
pure nothrow @system @nogc;
/**
* Allocates $(D_PARAM size) bytes of memory.
*
@ -28,29 +40,26 @@ class Mallocator : Allocator
*
* Returns: The pointer to the new allocated memory.
*/
void[] allocate(size_t size) shared @nogc nothrow
void[] allocate(const size_t size) shared pure nothrow @nogc
{
if (!size)
if (size == 0)
{
return null;
}
auto p = malloc(size + psize);
auto p = (cast(MallocType) &malloc)(size + psize);
if (!p)
{
onOutOfMemoryError();
}
return p[psize.. psize + size];
return p is null ? null : p[psize .. psize + size];
}
///
@nogc nothrow unittest
{
auto p = Mallocator.instance.allocate(20);
assert(p.length == 20);
Mallocator.instance.deallocate(p);
p = Mallocator.instance.allocate(0);
assert(p.length == 0);
}
/**
@ -61,11 +70,11 @@ class Mallocator : Allocator
*
* Returns: Whether the deallocation was successful.
*/
bool deallocate(void[] p) shared @nogc nothrow
bool deallocate(void[] p) shared pure nothrow @nogc
{
if (p !is null)
{
free(p.ptr - psize);
(cast(FreeType) &free)(p.ptr - psize);
}
return true;
}
@ -80,6 +89,28 @@ class Mallocator : Allocator
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, const size_t size)
shared pure nothrow @nogc
{
return false;
}
///
@nogc nothrow unittest
{
void[] p;
assert(!Mallocator.instance.reallocateInPlace(p, 8));
}
/**
* Increases or decreases the size of a memory block.
*
@ -89,54 +120,89 @@ class Mallocator : Allocator
*
* Returns: Whether the reallocation was successful.
*/
bool reallocate(ref void[] p, size_t size) shared @nogc nothrow
bool reallocate(ref void[] p, const size_t size) shared pure nothrow @nogc
{
if (!size)
if (size == 0)
{
if (deallocate(p))
{
deallocate(p);
p = null;
return true;
}
}
else if (p is null)
{
p = allocate(size);
return true;
return p is null ? false : true;
}
auto r = realloc(p.ptr - psize, size + psize);
if (!r)
else
{
onOutOfMemoryError();
}
p = r[psize.. psize + size];
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 unittest
{
void[] p;
Mallocator.instance.reallocate(p, 20);
assert(Mallocator.instance.reallocate(p, 20));
assert(p.length == 20);
Mallocator.instance.reallocate(p, 30);
assert(Mallocator.instance.reallocate(p, 30));
assert(p.length == 30);
Mallocator.instance.reallocate(p, 10);
assert(Mallocator.instance.reallocate(p, 10));
assert(p.length == 10);
Mallocator.instance.reallocate(p, 0);
assert(Mallocator.instance.reallocate(p, 0));
assert(p is null);
}
// Fails with false.
private @nogc nothrow unittest
{
void[] p = Mallocator.instance.allocate(20);
void[] oldP = p;
assert(!Mallocator.instance.reallocate(p, size_t.max - Mallocator.psize * 2));
assert(oldP is p);
Mallocator.instance.deallocate(p);
}
/**
* Returns: The alignment offered.
*/
@property immutable(uint) alignment() shared const @safe pure nothrow
@property uint alignment() shared const pure nothrow @safe @nogc
{
return cast(uint) max(double.alignof, real.alignof);
return (void*).alignof;
}
private nothrow @nogc unittest
{
assert(Mallocator.instance.alignment == (void*).alignof);
}
static private shared(Mallocator) instantiate() nothrow @nogc
{
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_;
}
/**
@ -144,21 +210,9 @@ class Mallocator : Allocator
*
* Returns: The global $(D_PSYMBOL Allocator) instance.
*/
static @property ref shared(Mallocator) instance() @nogc nothrow
static @property shared(Mallocator) instance() pure nothrow @nogc
{
if (instance_ is null)
{
immutable size = __traits(classInstanceSize, Mallocator) + psize;
void* p = malloc(size);
if (p is null)
{
onOutOfMemoryError();
}
p[psize..size] = typeid(Mallocator).initializer[];
instance_ = cast(shared Mallocator) p[psize..size].ptr;
}
return instance_;
return (cast(GetPureInstance!Mallocator) &instantiate)();
}
///
@ -167,7 +221,7 @@ class Mallocator : Allocator
assert(instance is instance);
}
private enum psize = 8;
private enum ushort psize = 8;
private shared static Mallocator instance_;
}

View File

@ -3,31 +3,87 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Copyright: Eugene Wissner 2016.
* Native allocator for Posix and Windows.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/mmappool.d,
* tanya/memory/mmappool.d)
*/
module tanya.memory.mmappool;
import std.algorithm.comparison;
import tanya.memory.allocator;
import core.atomic;
import core.exception;
import tanya.memory.op;
version (Posix)
{
import core.stdc.errno;
import core.sys.posix.sys.mman;
import core.sys.posix.sys.mman : MAP_ANON,
MAP_FAILED,
MAP_PRIVATE,
PROT_READ,
PROT_WRITE;
import core.sys.posix.unistd;
extern(C)
private void* mmap(void* addr,
size_t len,
int prot,
int flags,
int fd,
off_t offset) pure nothrow @system @nogc;
extern(C)
private int munmap(void* addr, size_t len) pure nothrow @system @nogc;
private void* mapMemory(const size_t len) pure nothrow @system @nogc
{
void* p = mmap(null,
len,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0);
return p is MAP_FAILED ? null : p;
}
private bool unmapMemory(shared void* addr, const size_t len)
pure nothrow @system @nogc
{
return munmap(cast(void*) addr, len) == 0;
}
}
else version (Windows)
{
import core.sys.windows.winbase;
import core.sys.windows.windows;
import core.sys.windows.winbase : GetSystemInfo, SYSTEM_INFO;
extern(Windows)
private void* VirtualAlloc(void*, size_t, uint, uint)
pure nothrow @system @nogc;
extern(Windows)
private int VirtualFree(void* addr, size_t len, uint)
pure nothrow @system @nogc;
private void* mapMemory(const size_t len) pure nothrow @system @nogc
{
return VirtualAlloc(null,
len,
0x00001000, // MEM_COMMIT
0x04); // PAGE_READWRITE
}
private bool unmapMemory(shared void* addr, const size_t len)
pure nothrow @system @nogc
{
return VirtualFree(cast(void*) addr, 0, 0x8000) == 0;
}
}
/**
* This allocator allocates memory in regions (multiple of 4 KB for example).
* This allocator allocates memory in regions (multiple of 64 KB for example).
* Each region is then splitted in blocks. So it doesn't request the memory
* from the operating system on each call, but only if there are no large
* enough free blocks in the available regions.
@ -36,7 +92,8 @@ else version (Windows)
* block as free and only if all blocks in the region are free, the complete
* region is deallocated.
*
* ----------------------------------------------------------------------------
* <pre>
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* | | | | | || | | |
* | |prev <----------- | || | | |
* | R | B | | B | || R | B | |
@ -46,32 +103,26 @@ else version (Windows)
* | O | K | | K | prev O | K | |
* | N | -----------> next| || N | | |
* | | | | | || | | |
* --------------------------------------------------- ------------------------
*
* TODO:
* $(UL
* $(LI Thread safety (core.atomic.cas))
* $(LI If two neighbour blocks are free, they can be merged)
* $(LI Reallocation shoud check if there is enough free space in the
* next block instead of always moving the memory)
* $(LI Make 64 KB regions mininmal region size on Linux)
* )
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* </pre>
*/
class MmapPool : Allocator
final class MmapPool : Allocator
{
@disable this();
shared static this()
version (none)
{
version (Posix)
pure nothrow @nogc invariant
{
pageSize = sysconf(_SC_PAGE_SIZE);
for (auto r = &head; *r !is null; r = &((*r).next))
{
auto block = cast(Block) (cast(void*) *r + RegionEntry.sizeof);
do
{
assert(block.prev is null || block.prev.next is block);
assert(block.next is null || block.next.prev is block);
assert(block.region is *r);
}
while ((block = block.next) !is null);
}
else version (Windows)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
pageSize = si.dwPageSize;
}
}
@ -81,15 +132,19 @@ class MmapPool : Allocator
* Params:
* size = Amount of memory to allocate.
*
* Returns: The pointer to the new allocated memory.
* Returns: Pointer to the new allocated memory.
*/
void[] allocate(size_t size) shared @nogc @trusted nothrow
void[] allocate(const size_t size) shared pure nothrow @nogc
{
if (!size)
if (size == 0)
{
return null;
}
const dataSize = addAlignment(size);
if (dataSize < size)
{
return null;
}
immutable dataSize = addAlignment(size);
void* data = findBlock(dataSize);
if (data is null)
@ -101,30 +156,49 @@ class MmapPool : Allocator
}
///
@nogc @safe nothrow unittest
nothrow unittest
{
auto p = MmapPool.instance.allocate(20);
assert(p);
MmapPool.instance.deallocate(p);
p = MmapPool.instance.allocate(0);
assert(p.length == 0);
}
/**
// Issue 245: https://issues.caraus.io/issues/245.
private @nogc unittest
{
// allocate() check.
size_t tooMuchMemory = size_t.max
- MmapPool.alignment_
- BlockEntry.sizeof * 2
- RegionEntry.sizeof
- MmapPool.instance.pageSize;
assert(MmapPool.instance.allocate(tooMuchMemory) is null);
assert(MmapPool.instance.allocate(size_t.max) is null);
// initializeRegion() check.
tooMuchMemory = size_t.max - MmapPool.alignment_;
assert(MmapPool.instance.allocate(tooMuchMemory) is null);
}
/*
* Search for a block large enough to keep $(D_PARAM size) and split it
* into two blocks if the block is too large.
*
* Params:
* size = Minimum size the block should have.
* size = Minimum size the block should have (aligned).
*
* Returns: Data the block points to or $(D_KEYWORD null).
*/
private void* findBlock(size_t size) shared @nogc nothrow
private void* findBlock(const ref size_t size) shared pure nothrow @nogc
{
Block block1;
RegionLoop: for (auto r = head; r !is null; r = r.next)
{
block1 = cast(Block) (cast(void*) r + regionEntrySize);
block1 = cast(Block) (cast(void*) r + RegionEntry.sizeof);
do
{
if (block1.free && block1.size >= size)
@ -138,35 +212,37 @@ class MmapPool : Allocator
{
return null;
}
else if (block1.size >= size + alignment + blockEntrySize)
else if (block1.size >= size + alignment_ + BlockEntry.sizeof)
{ // Split the block if needed
Block block2 = cast(Block) (cast(void*) block1 + blockEntrySize + size);
Block block2 = cast(Block) (cast(void*) block1 + BlockEntry.sizeof + size);
block2.prev = block1;
if (block1.next is null)
block2.next = block1.next;
block2.free = true;
block2.size = block1.size - BlockEntry.sizeof - size;
block2.region = block1.region;
if (block1.next !is null)
{
block2.next = null;
}
else
{
block2.next = block1.next.next;
block1.next.prev = block2;
}
block1.next = block2;
block1.free = false;
block2.free = true;
block2.size = block1.size - blockEntrySize - size;
block1.size = size;
block2.region = block1.region;
atomicOp!"+="(block1.region.blocks, 1);
}
else
{
block1.free = false;
atomicOp!"+="(block1.region.blocks, 1);
block1.region.blocks = block1.region.blocks + 1;
return cast(void*) block1 + BlockEntry.sizeof;
}
return cast(void*) block1 + blockEntrySize;
// Merge block with the next one.
private void mergeNext(Block block) shared const pure nothrow @safe @nogc
{
block.size = block.size + BlockEntry.sizeof + block.next.size;
if (block.next.next !is null)
{
block.next.next.prev = block;
}
block.next = block.next.next;
}
/**
@ -177,14 +253,14 @@ class MmapPool : Allocator
*
* Returns: Whether the deallocation was successful.
*/
bool deallocate(void[] p) shared @nogc @trusted nothrow
bool deallocate(void[] p) shared pure nothrow @nogc
{
if (p is null)
if (p.ptr is null)
{
return true;
}
Block block = cast(Block) (p.ptr - blockEntrySize);
Block block = cast(Block) (p.ptr - BlockEntry.sizeof);
if (block.region.blocks <= 1)
{
if (block.region.prev !is null)
@ -199,31 +275,139 @@ class MmapPool : Allocator
{
block.region.next.prev = block.region.prev;
}
version (Posix)
{
return munmap(cast(void*) block.region, block.region.size) == 0;
return unmapMemory(block.region, block.region.size);
}
version (Windows)
// Merge blocks if neigbours are free.
if (block.next !is null && block.next.free)
{
return VirtualFree(cast(void*) block.region, 0, MEM_RELEASE) == 0;
mergeNext(block);
}
if (block.prev !is null && block.prev.free)
{
block.prev.size = block.prev.size + BlockEntry.sizeof + block.size;
if (block.next !is null)
{
block.next.prev = block.prev;
}
block.prev.next = block.next;
}
else
{
block.free = true;
atomicOp!"-="(block.region.blocks, 1);
return true;
}
block.region.blocks = block.region.blocks - 1;
return true;
}
///
@nogc @safe nothrow unittest
nothrow unittest
{
auto p = MmapPool.instance.allocate(20);
assert(MmapPool.instance.deallocate(p));
}
/**
* Reallocates a memory block in place if possible or returns
* $(D_KEYWORD false). This function cannot be used to allocate or
* deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
* $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
*/
bool reallocateInPlace(ref void[] p, const size_t size)
shared pure nothrow @nogc
{
if (p is null || size == 0)
{
return false;
}
if (size <= p.length)
{
// Leave the block as is.
p = p.ptr[0 .. size];
return true;
}
Block block1 = cast(Block) (p.ptr - BlockEntry.sizeof);
if (block1.size >= size)
{
// Enough space in the current block.
p = p.ptr[0 .. size];
return true;
}
const dataSize = addAlignment(size);
const pAlignment = addAlignment(p.length);
assert(pAlignment >= p.length, "Invalid memory chunk length");
const delta = dataSize - pAlignment;
if (block1.next is null
|| !block1.next.free
|| dataSize < size
|| block1.next.size + BlockEntry.sizeof < delta)
{
/* - It is the last block in the region
* - The next block isn't free
* - The next block is too small
* - Requested size is too large
*/
return false;
}
if (block1.next.size >= delta + alignment_)
{
// Move size from block2 to block1.
block1.next.size = block1.next.size - delta;
block1.size = block1.size + delta;
auto block2 = cast(Block) (p.ptr + dataSize);
if (block1.next.next !is null)
{
block1.next.next.prev = block2;
}
copyBackward((cast(void*) block1.next)[0 .. BlockEntry.sizeof],
(cast(void*) block2)[0 .. BlockEntry.sizeof]);
block1.next = block2;
}
else
{
// The next block has enough space, but is too small for further
// allocations. Merge it with the current block.
mergeNext(block1);
}
p = p.ptr[0 .. size];
return true;
}
///
nothrow unittest
{
void[] p;
assert(!MmapPool.instance.reallocateInPlace(p, 5));
assert(p is null);
p = MmapPool.instance.allocate(1);
auto orig = p.ptr;
assert(MmapPool.instance.reallocateInPlace(p, 2));
assert(p.length == 2);
assert(p.ptr == orig);
assert(MmapPool.instance.reallocateInPlace(p, 4));
assert(p.length == 4);
assert(p.ptr == orig);
assert(MmapPool.instance.reallocateInPlace(p, 2));
assert(p.length == 2);
assert(p.ptr == orig);
MmapPool.instance.deallocate(p);
}
/**
* Increases or decreases the size of a memory block.
*
@ -233,33 +417,31 @@ class MmapPool : Allocator
*
* Returns: Whether the reallocation was successful.
*/
bool reallocate(ref void[] p, size_t size) shared @nogc @trusted nothrow
bool reallocate(ref void[] p, const size_t size) shared pure nothrow @nogc
{
void[] reallocP;
if (size == p.length)
if (size == 0)
{
if (deallocate(p))
{
p = null;
return true;
}
return false;
}
else if (reallocateInPlace(p, size))
{
return true;
}
else if (size > 0)
{
reallocP = allocate(size);
// Can't reallocate in place, allocate a new block,
// copy and delete the previous one.
void[] reallocP = allocate(size);
if (reallocP is null)
{
return false;
}
}
if (p !is null)
{
if (size > p.length)
{
reallocP[0..p.length] = p[0..$];
}
else if (size > 0)
{
reallocP[0..size] = p[0..size];
}
copy(p[0 .. min(p.length, size)], reallocP);
deallocate(p);
}
p = reallocP;
@ -268,7 +450,7 @@ class MmapPool : Allocator
}
///
@nogc @safe nothrow unittest
nothrow unittest
{
void[] p;
MmapPool.instance.reallocate(p, 10 * int.sizeof);
@ -296,36 +478,59 @@ class MmapPool : Allocator
MmapPool.instance.deallocate(p);
}
/**
* Static allocator instance and initializer.
*
* Returns: Global $(D_PSYMBOL MmapPool) instance.
*/
static @property ref shared(MmapPool) instance() @nogc @trusted nothrow
static private shared(MmapPool) instantiate() nothrow @nogc
{
if (instance_ is null)
{
immutable instanceSize = addAlignment(__traits(classInstanceSize, MmapPool));
// Get system dependend page size.
version (Posix)
{
size_t pageSize = sysconf(_SC_PAGE_SIZE);
if (pageSize < 65536)
{
pageSize = pageSize * 65536 / pageSize;
}
}
else version (Windows)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
size_t pageSize = si.dwPageSize;
}
const instanceSize = addAlignment(__traits(classInstanceSize,
MmapPool));
Region head; // Will become soon our region list head
void* data = initializeRegion(instanceSize, head);
void* data = initializeRegion(instanceSize, head, pageSize);
if (data !is null)
{
data[0..instanceSize] = typeid(MmapPool).initializer[];
copy(typeid(MmapPool).initializer, data[0 .. instanceSize]);
instance_ = cast(shared MmapPool) data;
instance_.head = head;
instance_.pageSize = pageSize;
}
}
return instance_;
}
/**
* Static allocator instance and initializer.
*
* Returns: Global $(D_PSYMBOL MmapPool) instance.
*/
static @property shared(MmapPool) instance() pure nothrow @nogc
{
return (cast(GetPureInstance!MmapPool) &instantiate)();
}
///
@nogc @safe nothrow unittest
nothrow unittest
{
assert(instance is instance);
}
/**
/*
* Initializes a region for one element.
*
* Params:
@ -334,44 +539,22 @@ class MmapPool : Allocator
*
* Returns: A pointer to the data.
*/
pragma(inline)
private static void* initializeRegion(size_t size,
ref Region head) @nogc nothrow
private static void* initializeRegion(const size_t size,
ref Region head,
const size_t pageSize)
pure nothrow @nogc
{
immutable regionSize = calculateRegionSize(size);
version (Posix)
const regionSize = calculateRegionSize(size, pageSize);
if (regionSize < size)
{
void* p = mmap(null,
regionSize,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0);
if (p is MAP_FAILED)
{
if (errno == ENOMEM)
{
onOutOfMemoryError();
}
return null;
}
}
else version (Windows)
{
void* p = VirtualAlloc(null,
regionSize,
MEM_COMMIT,
PAGE_READWRITE);
void* p = mapMemory(regionSize);
if (p is null)
{
if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY)
{
onOutOfMemoryError();
}
return null;
}
}
Region region = cast(Region) p;
region.blocks = 1;
@ -387,13 +570,13 @@ class MmapPool : Allocator
head = region;
// Initialize the data block
void* memoryPointer = p + regionEntrySize;
void* memoryPointer = p + RegionEntry.sizeof;
Block block1 = cast(Block) memoryPointer;
block1.size = size;
block1.free = false;
// It is what we want to return
void* data = memoryPointer + blockEntrySize;
void* data = memoryPointer + BlockEntry.sizeof;
// Free block after data
memoryPointer = data + size;
@ -401,65 +584,61 @@ class MmapPool : Allocator
block1.prev = block2.next = null;
block1.next = block2;
block2.prev = block1;
block2.size = regionSize - size - regionEntrySize - blockEntrySize * 2;
block2.size = regionSize - size - RegionEntry.sizeof - BlockEntry.sizeof * 2;
block2.free = true;
block1.region = block2.region = region;
return data;
}
/// Ditto.
private void* initializeRegion(size_t size) shared @nogc nothrow
private void* initializeRegion(const size_t size) shared pure nothrow @nogc
{
return initializeRegion(size, head);
return initializeRegion(size, this.head, this.pageSize);
}
/**
/*
* Params:
* x = Space to be aligned.
*
* Returns: Aligned size of $(D_PARAM x).
*/
pragma(inline)
private static immutable(size_t) addAlignment(size_t x)
@nogc @safe pure nothrow
out (result)
{
assert(result > 0);
}
body
private static size_t addAlignment(const size_t x) pure nothrow @safe @nogc
{
return (x - 1) / alignment_ * alignment_ + alignment_;
}
/**
/*
* Params:
* x = Required space.
* pageSize = Page size.
*
* Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)).
*/
pragma(inline)
private static immutable(size_t) calculateRegionSize(size_t x)
@nogc @safe pure nothrow
out (result)
private static size_t calculateRegionSize(ref const size_t x,
ref const size_t pageSize)
pure nothrow @safe @nogc
{
assert(result > 0);
}
body
{
x += regionEntrySize + blockEntrySize * 2;
return x / pageSize * pageSize + pageSize;
return (x + RegionEntry.sizeof + BlockEntry.sizeof * 2)
/ pageSize * pageSize + pageSize;
}
@property immutable(uint) alignment() shared const @nogc @safe pure nothrow
/**
* Returns: Alignment offered.
*/
@property uint alignment() shared const pure nothrow @safe @nogc
{
return alignment_;
}
private enum alignment_ = 8;
private nothrow @nogc unittest
{
assert(MmapPool.instance.alignment == MmapPool.alignment_);
}
private enum uint alignment_ = 8;
private shared static MmapPool instance_;
private shared static immutable size_t pageSize;
private shared size_t pageSize;
private shared struct RegionEntry
{
@ -469,18 +648,73 @@ class MmapPool : Allocator
size_t size;
}
private alias Region = shared RegionEntry*;
private enum regionEntrySize = 32;
private shared Region head;
private shared struct BlockEntry
{
Block prev;
Block next;
bool free;
size_t size;
Region region;
size_t size;
bool free;
}
private alias Block = shared BlockEntry*;
private enum blockEntrySize = 40;
}
// A lot of allocations/deallocations, but it is the minimum caused a
// segmentation fault because MmapPool reallocateInPlace moves a block wrong.
private @nogc unittest
{
auto a = MmapPool.instance.allocate(16);
auto d = MmapPool.instance.allocate(16);
auto b = MmapPool.instance.allocate(16);
auto e = MmapPool.instance.allocate(16);
auto c = MmapPool.instance.allocate(16);
auto f = MmapPool.instance.allocate(16);
MmapPool.instance.deallocate(a);
MmapPool.instance.deallocate(b);
MmapPool.instance.deallocate(c);
a = MmapPool.instance.allocate(50);
MmapPool.instance.reallocateInPlace(a, 64);
MmapPool.instance.deallocate(a);
a = MmapPool.instance.allocate(1);
auto tmp1 = MmapPool.instance.allocate(1);
auto h1 = MmapPool.instance.allocate(1);
auto tmp2 = cast(ubyte[]) MmapPool.instance.allocate(1);
auto h2 = MmapPool.instance.allocate(2);
tmp1 = MmapPool.instance.allocate(1);
MmapPool.instance.deallocate(h2);
MmapPool.instance.deallocate(h1);
h2 = MmapPool.instance.allocate(2);
h1 = MmapPool.instance.allocate(1);
MmapPool.instance.deallocate(h2);
auto rep = cast(void[]) tmp2;
MmapPool.instance.reallocate(rep, tmp1.length);
tmp2 = cast(ubyte[]) rep;
MmapPool.instance.reallocate(tmp1, 9);
rep = cast(void[]) tmp2;
MmapPool.instance.reallocate(rep, tmp1.length);
tmp2 = cast(ubyte[]) rep;
MmapPool.instance.reallocate(tmp1, 17);
tmp2[$ - 1] = 0;
MmapPool.instance.deallocate(tmp1);
b = MmapPool.instance.allocate(16);
MmapPool.instance.deallocate(h1);
MmapPool.instance.deallocate(a);
MmapPool.instance.deallocate(b);
MmapPool.instance.deallocate(d);
MmapPool.instance.deallocate(e);
MmapPool.instance.deallocate(f);
}

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

@ -0,0 +1,385 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Set of operations on memory blocks.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/op.d,
* tanya/memory/op.d)
*/
module tanya.memory.op;
version (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 int cmpMemory(const void[], const void[])
pure nothrow @system @nogc;
}
else
{
import core.stdc.string;
}
private enum alignMask = size_t.sizeof - 1;
/**
* Copies $(D_PARAM source) into $(D_PARAM target).
*
* $(D_PARAM source) and $(D_PARAM target) shall not overlap so that
* $(D_PARAM source) points ahead of $(D_PARAM target).
*
* $(D_PARAM target) shall have enough space for $(D_INLINECODE source.length)
* elements.
*
* Params:
* source = Memory to copy from.
* target = Destination memory.
*
* See_Also: $(D_PSYMBOL copyBackward).
*
* Precondition: $(D_INLINECODE source.length <= target.length).
*/
void copy(const void[] source, void[] target) @nogc nothrow pure @trusted
in
{
assert(source.length <= target.length);
assert(source.length == 0 || source.ptr !is null);
assert(target.length == 0 || target.ptr !is null);
}
body
{
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(cmp(source, target) == 0);
}
@nogc nothrow pure @safe unittest
{
{
ubyte[0] source, target;
source.copy(target);
}
{
ubyte[1] source = [1];
ubyte[1] target;
source.copy(target);
assert(target[0] == 1);
}
{
ubyte[8] source = [1, 2, 3, 4, 5, 6, 7, 8];
ubyte[8] target;
source.copy(target);
assert(cmp(source, target) == 0);
}
}
/*
* 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);
}
body
{
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);
}
}
// Stress test. Checks that `fill` can handle unaligned pointers and different
// lengths.
@nogc nothrow pure @safe unittest
{
ubyte[192] memory;
foreach (j; 0 .. 192)
{
foreach (ubyte i, ref ubyte v; memory[j .. $])
{
v = i;
}
fill(memory[j .. $]);
foreach (ubyte v; memory[j .. $])
{
assert(v == 0);
}
fill!1(memory[j .. $]);
foreach (ubyte v; memory[j .. $])
{
assert(v == 1);
}
}
}
/**
* Copies starting from the end of $(D_PARAM source) into the end of
* $(D_PARAM target).
*
* $(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);
}
body
{
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(cmp(expected, mem) == 0);
}
@nogc nothrow pure @safe unittest
{
ubyte[9] r1 = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i' ];
ubyte[9] r2;
copyBackward(r1, r2);
assert(cmp(r1, r2) == 0);
}
/**
* Compares two memory areas $(D_PARAM r1) and $(D_PARAM r2).
*
* $(D_PSYMBOL cmp) returns a positive integer if
* $(D_INLINECODE r1.length > r2.length) or the first `n` compared bytes of
* $(D_PARAM r1) found to be greater than the first `n` bytes of $(D_PARAM r2),
*
* $(D_PSYMBOL cmp) returns a negative integer if
* $(D_INLINECODE r2.length > r1.length) or the first `n` compared bytes of
* $(D_PARAM r1) found to be less than the first `n` bytes of $(D_PARAM r2),
*
* `0` is returned otherwise.
*
* Returns: Positive integer if $(D_INLINECODE r1 > r2),
* negative integer if $(D_INLINECODE r2 > r1),
* `0` if $(D_INLINECODE r1 == r2).
*/
int cmp(const void[] r1, const void[] r2) @nogc nothrow pure @trusted
in
{
assert(r1.length == 0 || r1.ptr !is null);
assert(r2.length == 0 || r2.ptr !is null);
}
body
{
version (TanyaNative)
{
return cmpMemory(r1, r2);
}
else
{
if (r1.length > r2.length)
{
return 1;
}
return r1.length < r2.length ? -1 : memcmp(r1.ptr, r2.ptr, r1.length);
}
}
///
@nogc nothrow pure @safe unittest
{
ubyte[4] r1 = [ 'a', 'b', 'c', 'd' ];
ubyte[3] r2 = [ 'c', 'a', 'b' ];
assert(cmp(r1[0 .. 3], r2[]) < 0);
assert(cmp(r2[], r1[0 .. 3]) > 0);
assert(cmp(r1, r2) > 0);
assert(cmp(r2, r1) < 0);
}
@nogc nothrow pure @safe unittest
{
ubyte[16] r1 = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
];
ubyte[16] r2 = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
];
assert(cmp(r1, r2) == 0);
assert(cmp(r1[1 .. $], r2[1 .. $]) == 0);
assert(cmp(r1[0 .. $ - 1], r2[0 .. $ - 1]) == 0);
assert(cmp(r1[0 .. 8], r2[0 .. 8]) == 0);
}
/**
* 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, const ubyte needle)
@nogc nothrow pure @trusted
in
{
assert(haystack.length == 0 || haystack.ptr !is null);
}
body
{
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)
{
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(find(haystack, 'a') == haystack[]);
assert(find(haystack, 'b') == haystack[1 .. $]);
assert(find(haystack, 'c') == haystack[2 .. $]);
assert(find(haystack, 'd') == haystack[3 .. $]);
assert(find(haystack, 'e') == haystack[4 .. $]);
assert(find(haystack, 'f') == haystack[5 .. $]);
assert(find(haystack, 'h') == haystack[8 .. $]);
assert(find(haystack, 'i').length == 0);
assert(find(null, 'a').length == 0);
}

View File

@ -3,27 +3,499 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Copyright: Eugene Wissner 2016.
* Dynamic memory management.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/package.d,
* tanya/memory/package.d)
*/
module tanya.memory;
public
import std.algorithm.iteration;
import std.algorithm.mutation;
import tanya.conv;
import tanya.exception;
public import tanya.memory.allocator;
import tanya.memory.mmappool;
import tanya.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()
{
import tanya.memory.allocator;
import std.experimental.allocator : make, dispose, shrinkArray, expandArray, makeArray, dispose;
/// Allocator.
protected shared Allocator allocator_;
/**
* Params:
* allocator = The allocator should be used.
*
* Precondition: $(D_INLINECODE allocator_ !is null)
*/
this(shared Allocator allocator) pure nothrow @safe @nogc
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() pure nothrow @safe @nogc
out (allocator)
{
assert(allocator !is null);
}
body
{
if (allocator_ is null)
{
allocator_ = defaultAllocator;
}
return allocator_;
}
/// ditto
@property shared(Allocator) allocator() const pure nothrow @trusted @nogc
out (allocator)
{
assert(allocator !is null);
}
body
{
if (allocator_ is null)
{
return defaultAllocator;
}
return cast(shared Allocator) allocator_;
}
}
// From druntime
extern (C)
private void _d_monitordelete(Object h, bool det) pure nothrow @nogc;
shared Allocator allocator;
@property ref shared(Allocator) defaultAllocator()
shared static this() nothrow @nogc
{
import tanya.memory.mallocator;
if (allocator is null)
{
allocator = Mallocator.instance;
allocator = MmapPool.instance;
}
private shared(Allocator) getAllocatorInstance() nothrow @nogc
{
return allocator;
}
/**
* Returns: Default allocator.
*
* Postcondition: $(D_INLINECODE allocator !is null).
*/
@property shared(Allocator) defaultAllocator() pure nothrow @trusted @nogc
out (allocator)
{
assert(allocator !is null);
}
body
{
return (cast(GetPureInstance!Allocator) &getAllocatorInstance)();
}
/**
* Sets the default allocator.
*
* Params:
* allocator = $(D_PSYMBOL Allocator) instance.
*
* Precondition: $(D_INLINECODE allocator !is null).
*/
@property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc
in
{
assert(allocator !is null);
}
body
{
.allocator = allocator;
}
/**
* Returns the size in bytes of the state that needs to be allocated to hold an
* object of type $(D_PARAM T).
*
* There is a difference between the `.sizeof`-property and
* $(D_PSYMBOL stateSize) if $(D_PARAM T) is a class or an interface.
* `T.sizeof` is constant on the given architecture then and is the same as
* `size_t.sizeof` and `ptrdiff_t.sizeof`. This is because classes and
* interfaces are reference types and `.sizeof` returns the size of the
* reference which is the same as the size of a pointer. $(D_PSYMBOL stateSize)
* returns the size of the instance itself.
*
* The size of a dynamic array is `size_t.sizeof * 2` since a dynamic array
* stores its length and a data pointer. The size of the static arrays is
* calculated differently since they are value types. It is the array length
* multiplied by the element size.
*
* `stateSize!void` is `1` since $(D_KEYWORD void) is mostly used as a synonym
* for $(D_KEYWORD byte)/$(D_KEYWORD ubyte) in `void*`.
*
* Params:
* T = Object type.
*
* Returns: Size of an instance of type $(D_PARAM T).
*/
template stateSize(T)
{
static if (isPolymorphicType!T)
{
enum size_t stateSize = __traits(classInstanceSize, T);
}
else
{
enum size_t stateSize = T.sizeof;
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(stateSize!int == 4);
static assert(stateSize!bool == 1);
static assert(stateSize!(int[]) == (size_t.sizeof * 2));
static assert(stateSize!(short[3]) == 6);
static struct Empty
{
}
static assert(stateSize!Empty == 1);
static assert(stateSize!void == 1);
}
/**
* Params:
* size = Raw size.
* alignment = Alignment.
*
* Returns: Aligned size.
*/
size_t alignedSize(const size_t size, const size_t alignment = 8)
pure nothrow @safe @nogc
{
return (size - 1) / alignment * alignment + alignment;
}
/*
* Internal function used to create, resize or destroy a dynamic array. It
* may throw $(D_PSYMBOL OutOfMemoryError). The new
* allocated part of the array isn't initialized. This function can be trusted
* only in the data structures that can ensure that the array is
* allocated/rellocated/deallocated with the same allocator.
*
* Params:
* T = Element type of the array being created.
* allocator = The allocator used for getting memory.
* array = A reference to the array being changed.
* length = New array length.
*
* Returns: $(D_PARAM array).
*/
package(tanya) T[] resize(T)(shared Allocator allocator,
auto ref T[] array,
const size_t length) @trusted
{
if (length == 0)
{
if (allocator.deallocate(array))
{
return null;
}
else
{
onOutOfMemoryError();
}
}
void[] buf = array;
if (!allocator.reallocate(buf, length * T.sizeof))
{
onOutOfMemoryError();
}
// Casting from void[] is unsafe, but we know we cast to the original type.
array = cast(T[]) buf;
return array;
}
private unittest
{
int[] p;
p = defaultAllocator.resize(p, 20);
assert(p.length == 20);
p = defaultAllocator.resize(p, 30);
assert(p.length == 30);
p = defaultAllocator.resize(p, 10);
assert(p.length == 10);
p = defaultAllocator.resize(p, 0);
assert(p is null);
}
/*
* Destroys the object.
* Returns the memory should be freed.
*/
package(tanya) void[] finalize(T)(ref T* p)
{
static if (hasElaborateDestructor!T)
{
destroy(*p);
}
return (cast(void*) p)[0 .. T.sizeof];
}
package(tanya) void[] finalize(T)(ref T p)
if (is(T == class) || is(T == interface))
{
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)
{
static if (hasElaborateDestructor!(typeof(p[0])))
{
p.each!((ref e) => destroy(e));
}
return p;
}
/**
* Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T).
* It is assumed the respective entities had been allocated with the same
* allocator.
*
* Params:
* T = Type of $(D_PARAM p).
* allocator = Allocator the $(D_PARAM p) was allocated with.
* p = Object or array to be destroyed.
*/
void dispose(T)(shared Allocator allocator, auto ref T p)
{
() @trusted { allocator.deallocate(finalize(p)); }();
p = null;
}
private unittest
{
struct S
{
~this()
{
}
}
auto p = cast(S[]) defaultAllocator.allocate(S.sizeof);
defaultAllocator.dispose(p);
}
// Works with interfaces.
private pure unittest
{
interface I
{
}
class C : I
{
}
auto c = defaultAllocator.make!C();
I i = c;
defaultAllocator.dispose(i);
defaultAllocator.dispose(i);
}
/**
* Constructs a new class instance of type $(D_PARAM T) using $(D_PARAM args)
* as the parameter list for the constructor of $(D_PARAM T).
*
* Params:
* T = Class type.
* A = Types of the arguments to the constructor of $(D_PARAM T).
* allocator = Allocator.
* args = Constructor arguments of $(D_PARAM T).
*
* Returns: Newly created $(D_PSYMBOL T).
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
T make(T, A...)(shared Allocator allocator, auto ref A args)
if (is(T == class))
in
{
assert(allocator !is null);
}
body
{
auto mem = (() @trusted => allocator.allocate(stateSize!T))();
if (mem is null)
{
onOutOfMemoryError();
}
scope (failure)
{
() @trusted { allocator.deallocate(mem); }();
}
return emplace!T(mem[0 .. stateSize!T], args);
}
/**
* Constructs a value object of type $(D_PARAM T) using $(D_PARAM args)
* as the parameter list for the constructor of $(D_PARAM T) and returns a
* pointer to the new object.
*
* Params:
* T = Object type.
* A = Types of the arguments to the constructor of $(D_PARAM T).
* allocator = Allocator.
* args = Constructor arguments of $(D_PARAM T).
*
* Returns: Pointer to the created object.
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
T* make(T, A...)(shared Allocator allocator, auto ref A args)
if (!is(T == interface)
&& !is(T == class)
&& !isAssociativeArray!T
&& !isArray!T)
in
{
assert(allocator !is null);
}
body
{
auto mem = (() @trusted => allocator.allocate(stateSize!T))();
if (mem is null)
{
onOutOfMemoryError();
}
scope (failure)
{
() @trusted { allocator.deallocate(mem); }();
}
return emplace!T(mem[0 .. stateSize!T], args);
}
///
unittest
{
int* i = defaultAllocator.make!int(5);
assert(*i == 5);
defaultAllocator.dispose(i);
}
/**
* Constructs a new array with $(D_PARAM n) elements.
*
* Params:
* T = Array type.
* allocator = Allocator.
* n = Array size.
*
* Returns: Newly created array.
*
* Precondition: $(D_INLINECODE allocator !is null
* && n <= size_t.max / ElementType!T.sizeof)
*/
T make(T)(shared Allocator allocator, const size_t n)
if (isArray!T)
in
{
assert(allocator !is null);
assert(n <= size_t.max / ElementType!T.sizeof);
}
body
{
auto ret = allocator.resize!(ElementType!T)(null, n);
ret.uninitializedFill(ElementType!T.init);
return ret;
}
///
unittest
{
int[] i = defaultAllocator.make!(int[])(2);
assert(i.length == 2);
assert(i[0] == int.init && i[1] == int.init);
defaultAllocator.dispose(i);
}

View File

@ -0,0 +1,923 @@
/* 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-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/smartref.d,
* tanya/memory/smartref.d)
*/
module tanya.memory.smartref;
import std.algorithm.comparison;
import tanya.algorithm.mutation;
import tanya.conv;
import tanya.exception;
import tanya.memory;
import tanya.meta.trait;
import tanya.range.primitive;
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);
}
body
{
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);
}
body
{
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");
}
body
{
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);
}
body
{
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);
}
body
{
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
{
static bool destroyed;
static struct F
{
~this() @nogc nothrow @safe
{
destroyed = true;
}
}
{
auto rc = defaultAllocator.refCounted!F();
}
assert(destroyed);
}
/**
* $(D_PSYMBOL Unique) stores an object that gets destroyed at the end of its scope.
*
* 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);
}
body
{
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);
}
body
{
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);
}
body
{
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);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
/* 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/. */
/**
* 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.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/meta/package.d,
* tanya/meta/package.d)
*/
module tanya.meta;
public import tanya.meta.metafunction;
public import tanya.meta.trait;
public import tanya.meta.transform;

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

File diff suppressed because it is too large Load Diff

View File

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

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

@ -0,0 +1,246 @@
/* 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-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/net/inet.d,
* tanya/net/inet.d)
*/
module tanya.net.inet;
import std.math;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range.primitive;
/**
* 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;
const pure nothrow @safe @nogc 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 ^^ (length * 8) - 1).
*/
this(T)(const T value)
if (isUnsigned!T)
in
{
assert(value <= pow(2, L * 8) - 1);
}
body
{
this.value = value & StorageType.max;
}
/**
* Returns: LSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
@property ubyte back() const
in
{
assert(this.length > 0);
}
body
{
return this.value & 0xff;
}
/**
* Returns: MSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
@property ubyte front() const
in
{
assert(this.length > 0);
}
body
{
return (this.value >> ((this.length - 1) * 8)) & 0xff;
}
/**
* Eliminates the LSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
void popBack()
in
{
assert(this.length > 0);
}
body
{
this.value >>= 8;
--this.size;
}
/**
* Eliminates the MSB.
*
* Precondition: $(D_INLINECODE length > 0).
*/
void popFront()
in
{
assert(this.length > 0);
}
body
{
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;
}
}
///
pure nothrow @safe @nogc 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.
private unittest
{
static assert(isBidirectionalRange!(NetworkOrder!4));
static assert(isBidirectionalRange!(NetworkOrder!8));
static assert(!is(NetworkOrder!9));
static assert(!is(NetworkOrder!1));
}
/**
* Converts the $(D_KEYWORD ubyte) input range $(D_PARAM range) to
* $(D_PARAM T).
*
* 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;
}
///
pure nothrow @safe @nogc unittest
{
const value = 0xae34e2u;
auto networkOrder = NetworkOrder!4(value);
assert(networkOrder.toHostOrder() == value);
}

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

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

@ -0,0 +1,513 @@
/* 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.
* 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.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 @safe pure nothrow
{
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) pure @nogc
{
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
{
// Schemas like mailto: and zlib: may not have any slash after
// them.
if (!parsePort(source[pos .. $]))
{
this.scheme = source[0 .. pos];
start = pos + 1;
goto ParsePath;
}
}
}
else if (pos == 0 && parsePort(source[pos .. $]))
{
// An URL shouldn't begin with a port number.
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) pure nothrow @safe @nogc
{
ptrdiff_t i = 1;
float lPort = 0;
for (; i < port.length && port[i].isDigit() && i <= 6; ++i)
{
lPort += (port[i] - '0') / cast(float) (10 ^^ (i - 1));
}
if (i != 1 && (i == port.length || port[i] == '/'))
{
lPort *= 10 ^^ (i - 2);
if (lPort > ushort.max)
{
return false;
}
this.port = cast(ushort) lPort;
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"));
}
// Issue 254: https://issues.caraus.io/issues/254.
@nogc pure @system unittest
{
auto u = URL("ftp://");
assert(u.scheme == "ftp");
}
/**
* Attempts to parse an URL from a string and returns the specified component
* of the URL or $(D_PSYMBOL URL) if no component is specified.
*
* 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

@ -3,16 +3,15 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Copyright: Eugene Wissner 2016.
* Network programming.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto: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/network/package.d,
* tanya/network/package.d)
*/
module tanya.crypto;
module tanya.network;
public
{
import tanya.crypto.des;
import tanya.crypto.mode;
import tanya.crypto.symmetric;
}
public import tanya.network.socket;

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,339 @@
/* 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.
* 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;
// 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 orR 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),
}
/**
* Constructor.
*
* Params:
* value = Numeric error code.
*/
this(const ErrorNo value) pure nothrow @safe @nogc
{
this.value_ = value;
}
///
pure nothrow @safe @nogc 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() pure nothrow @safe @nogc
{
this.value_ = ErrorNo.success;
}
///
pure nothrow @safe @nogc 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_;
}
///
pure nothrow @safe @nogc 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) pure nothrow @safe @nogc
{
this.value_ = that;
return this;
}
/// ditto
ref ErrorCode opAssign()(auto ref const ErrorCode that)
pure nothrow @safe @nogc
{
this.value_ = that.value_;
return this;
}
///
pure nothrow @safe @nogc unittest
{
{
ErrorCode ec;
assert(ec == ErrorCode.success);
ec = ErrorCode.fault;
assert(ec == ErrorCode.fault);
}
{
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 pure nothrow @safe @nogc
{
return this.value_ == that;
}
/// ditto
bool opEquals()(auto ref const ErrorCode that)
const pure nothrow @safe @nogc
{
return this.value_ == that.value_;
}
///
pure nothrow @safe @nogc unittest
{
{
ErrorCode ec1 = ErrorCode.fault;
ErrorCode ec2 = ErrorCode.accessDenied;
assert(ec1 != ec2);
assert(ec1 != ErrorCode.accessDenied);
assert(ErrorCode.fault != ec2);
}
{
ErrorCode ec1 = ErrorCode.fault;
ErrorCode ec2 = ErrorCode.fault;
assert(ec1 == ec2);
assert(ec1 == ErrorCode.fault);
assert(ErrorCode.fault == ec2);
}
}
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.
* 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

@ -1,318 +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.random;
import tanya.memory;
import std.digest.sha;
import std.typecons;
/// Block size of entropy accumulator (SHA-512).
enum blockSize = 64;
/// Maximum amount gathered from the entropy sources.
enum maxGather = 128;
/**
* Exception thrown if random number generating fails.
*/
class EntropyException : Exception
{
/**
* Params:
* msg = Message to output.
* 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) pure @safe nothrow const @nogc
{
super(msg, file, line, next);
}
}
/**
* Interface for implementing entropy sources.
*/
abstract class EntropySource
{
/// Amount of already generated entropy.
protected ushort size_;
/**
* Returns: Minimum bytes required from the entropy source.
*/
@property immutable(ubyte) threshold() const @safe pure nothrow;
/**
* Returns: Whether this entropy source is strong.
*/
@property immutable(bool) strong() const @safe pure nothrow;
/**
* Returns: Amount of already generated entropy.
*/
@property ushort size() const @safe pure nothrow
{
return size_;
}
/**
* Params:
* size = Amount of already generated entropy. Cannot be smaller than the
* already set value.
*/
@property void size(ushort size) @safe pure nothrow
{
size_ = size;
}
/**
* Poll the entropy source.
*
* Params:
* output = Buffer to save the generate random sequence (the method will
* to fill the buffer).
*
* Returns: Number of bytes that were copied to the $(D_PARAM output)
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
*/
Nullable!ubyte poll(out ubyte[maxGather] output);
}
version (linux)
{
extern (C) long syscall(long number, ...) nothrow;
/**
* Uses getrandom system call.
*/
class PlatformEntropySource : EntropySource
{
/**
* Returns: Minimum bytes required from the entropy source.
*/
override @property immutable(ubyte) threshold() const @safe pure nothrow
{
return 32;
}
/**
* Returns: Whether this entropy source is strong.
*/
override @property immutable(bool) strong() const @safe pure nothrow
{
return true;
}
/**
* Poll the entropy source.
*
* Params:
* output = Buffer to save the generate random sequence (the method will
* to fill the buffer).
*
* Returns: Number of bytes that were copied to the $(D_PARAM output)
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
*/
override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow
out (length)
{
assert(length <= maxGather);
}
body
{
// 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)
{
ret = cast(ubyte) length;
}
return ret;
}
}
}
/**
* Pseudorandom number generator.
* ---
* auto entropy = defaultAllocator.make!Entropy;
*
* ubyte[blockSize] output;
*
* output = entropy.random;
*
* defaultAllocator.finalize(entropy);
* ---
*/
class Entropy
{
/// Entropy sources.
protected EntropySource[] sources;
private ubyte sourceCount_;
private shared Allocator allocator;
/// Entropy accumulator.
protected SHA!(maxGather * 8, 512) accumulator;
/**
* Params:
* maxSources = Maximum amount of entropy sources can be set.
* allocator = Allocator to allocate entropy sources available on the
* system.
*/
this(size_t maxSources = 20, shared Allocator allocator = defaultAllocator)
in
{
assert(maxSources > 0 && maxSources <= ubyte.max);
assert(allocator !is null);
}
body
{
allocator.resizeArray(sources, maxSources);
version (linux)
{
this ~= allocator.make!PlatformEntropySource;
}
}
/**
* Returns: Amount of the registered entropy sources.
*/
@property ubyte sourceCount() const @safe pure nothrow
{
return sourceCount_;
}
/**
* Add an entropy source.
*
* Params:
* source = Entropy source.
*
* Returns: $(D_PSYMBOL this).
*
* See_Also:
* $(D_PSYMBOL EntropySource)
*/
Entropy opOpAssign(string Op)(EntropySource source) @safe pure nothrow
if (Op == "~")
in
{
assert(sourceCount_ <= sources.length);
}
body
{
sources[sourceCount_++] = source;
return this;
}
/**
* Returns: Generated random sequence.
*
* Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was
* registered or it failed.
*/
@property ubyte[blockSize] random()
in
{
assert(sourceCount_ > 0, "No entropy sources defined.");
}
body
{
bool haveStrong;
ushort done;
ubyte[blockSize] output;
do
{
ubyte[maxGather] buffer;
// Run through our entropy sources
for (ubyte i; i < sourceCount; ++i)
{
auto outputLength = sources[i].poll(buffer);
if (!outputLength.isNull)
{
if (outputLength > 0)
{
update(i, buffer, outputLength);
sources[i].size = cast(ushort) (sources[i].size + outputLength);
}
if (sources[i].size < sources[i].threshold)
{
continue;
}
else if (sources[i].strong)
{
haveStrong = true;
}
}
done = 257;
}
}
while (++done < 256);
if (!haveStrong)
{
throw allocator.make!EntropyException("No strong entropy source defined.");
}
output = accumulator.finish();
// Reset accumulator and counters and recycle existing entropy
accumulator.start();
// Perform second SHA-512 on entropy
output = sha512Of(output);
for (ubyte i = 0; i < sourceCount; ++i)
{
sources[i].size = 0;
}
return output;
}
/**
* Update entropy accumulator.
*
* Params:
* sourceId = Entropy source index in $(D_PSYMBOL sources).
* data = Data got from the entropy source.
* length = Length of the received data.
*/
protected void update(in ubyte sourceId,
ref ubyte[maxGather] data,
ubyte length) @safe pure nothrow
{
ubyte[2] header;
if (length > blockSize)
{
data[0..64] = sha512Of(data);
length = blockSize;
}
header[0] = sourceId;
header[1] = length;
accumulator.put(header);
accumulator.put(data[0..length]);
}
}

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.
* 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 T front(T)(T[] array)
in
{
assert(array.length > 0);
}
body
{
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 T back(T)(T[] array)
in
{
assert(array.length > 0);
}
body
{
return array[$ - 1];
}
///
@nogc nothrow pure @safe unittest
{
string s = "Brecht";
static assert(is(typeof(s.back) == immutable char));
assert(s.back == 't');
wstring w = "Тютчев";
static assert(is(typeof(w.back) == immutable wchar));
assert(w.back == 'в');
dstring d = "Паустовский";
static assert(is(typeof(d.back) == immutable dchar));
assert(d.back == 'й');
}
/**
* $(D_PSYMBOL popFront) and $(D_PSYMBOL popBack) advance the $(D_PARAM array)
* and remove one element from its back, respectively.
*
* $(D_PSYMBOL popFront) and $(D_PSYMBOL popBack) don't alter the array
* storage, they only narrow the view into the array.
*
* Params:
* T = Element type of $(D_PARAM array).
* array = Built-in array.
*
* Precondition: $(D_INLINECODE array.length > 0).
*/
void popFront(T)(ref T[] array)
in
{
assert(array.length > 0);
}
body
{
array = array[1 .. $];
}
/// ditto
void popBack(T)(ref T[] array)
in
{
assert(array.length > 0);
}
body
{
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)(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 T[] save(T)(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,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/. */
/**
* This package contains generic functions and templates to be used with D
* ranges.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/range/package.d,
* tanya/range/package.d)
*/
module tanya.range;
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/. */
/**
* 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.
* 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 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;
align(1) struct GUID
{
uint Data1;
ushort Data2;
ushort Data3;
char[8] Data4;
}

View File

@ -0,0 +1,114 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/sys/windows/error.d,
* tanya/sys/windows/error.d)
*/
module tanya.sys.windows.error;
version (Windows):
private enum WSABASEERR = 10000;
enum
{
WSAEINTR = WSABASEERR + 4,
WSAEBADF = WSABASEERR + 9,
WSAEACCES = WSABASEERR + 13,
WSAEFAULT = WSABASEERR + 14,
WSAEINVAL = WSABASEERR + 22,
WSAEMFILE = WSABASEERR + 24,
WSAEWOULDBLOCK = WSABASEERR + 35,
WSAEINPROGRESS = WSABASEERR + 36,
WSAEALREADY = WSABASEERR + 37,
WSAENOTSOCK = WSABASEERR + 38,
WSAEDESTADDRREQ = WSABASEERR + 39,
WSAEMSGSIZE = WSABASEERR + 40,
WSAEPROTOTYPE = WSABASEERR + 41,
WSAENOPROTOOPT = WSABASEERR + 42,
WSAEPROTONOSUPPORT = WSABASEERR + 43,
WSAESOCKTNOSUPPORT = WSABASEERR + 44,
WSAEOPNOTSUPP = WSABASEERR + 45,
WSAEPFNOSUPPORT = WSABASEERR + 46,
WSAEAFNOSUPPORT = WSABASEERR + 47,
WSAEADDRINUSE = WSABASEERR + 48,
WSAEADDRNOTAVAIL = WSABASEERR + 49,
WSAENETDOWN = WSABASEERR + 50,
WSAENETUNREACH = WSABASEERR + 51,
WSAENETRESET = WSABASEERR + 52,
WSAECONNABORTED = WSABASEERR + 53,
WSAECONNRESET = WSABASEERR + 54,
WSAENOBUFS = WSABASEERR + 55,
WSAEISCONN = WSABASEERR + 56,
WSAENOTCONN = WSABASEERR + 57,
WSAESHUTDOWN = WSABASEERR + 58,
WSAETOOMANYREFS = WSABASEERR + 59,
WSAETIMEDOUT = WSABASEERR + 60,
WSAECONNREFUSED = WSABASEERR + 61,
WSAELOOP = WSABASEERR + 62,
WSAENAMETOOLONG = WSABASEERR + 63,
WSAEHOSTDOWN = WSABASEERR + 64,
WSAEHOSTUNREACH = WSABASEERR + 65,
WSAENOTEMPTY = WSABASEERR + 66,
WSAEPROCLIM = WSABASEERR + 67,
WSAEUSERS = WSABASEERR + 68,
WSAEDQUOT = WSABASEERR + 69,
WSAESTALE = WSABASEERR + 70,
WSAEREMOTE = WSABASEERR + 71,
WSASYSNOTREADY = WSABASEERR + 91,
WSAVERNOTSUPPORTED = WSABASEERR + 92,
WSANOTINITIALISED = WSABASEERR + 93,
WSAEDISCON = WSABASEERR + 101,
WSAENOMORE = WSABASEERR + 102,
WSAECANCELLED = WSABASEERR + 103,
WSAEINVALIDPROCTABLE = WSABASEERR + 104,
WSAEINVALIDPROVIDER = WSABASEERR + 105,
WSAEPROVIDERFAILEDINIT = WSABASEERR + 106,
WSASYSCALLFAILURE = WSABASEERR + 107,
WSASERVICE_NOT_FOUND = WSABASEERR + 108,
WSATYPE_NOT_FOUND = WSABASEERR + 109,
WSA_E_NO_MORE = WSABASEERR + 110,
WSA_E_CANCELLED = WSABASEERR + 111,
WSAEREFUSED = WSABASEERR + 112,
WSAHOST_NOT_FOUND = WSABASEERR + 1001,
WSATRY_AGAIN = WSABASEERR + 1002,
WSANO_RECOVERY = WSABASEERR + 1003,
WSANO_DATA = WSABASEERR + 1004,
WSA_QOS_RECEIVERS = WSABASEERR + 1005,
WSA_QOS_SENDERS = WSABASEERR + 1006,
WSA_QOS_NO_SENDERS = WSABASEERR + 1007,
WSA_QOS_NO_RECEIVERS = WSABASEERR + 1008,
WSA_QOS_REQUEST_CONFIRMED = WSABASEERR + 1009,
WSA_QOS_ADMISSION_FAILURE = WSABASEERR + 1010,
WSA_QOS_POLICY_FAILURE = WSABASEERR + 1011,
WSA_QOS_BAD_STYLE = WSABASEERR + 1012,
WSA_QOS_BAD_OBJECT = WSABASEERR + 1013,
WSA_QOS_TRAFFIC_CTRL_ERROR = WSABASEERR + 1014,
WSA_QOS_GENERIC_ERROR = WSABASEERR + 1015,
WSA_QOS_ESERVICETYPE = WSABASEERR + 1016,
WSA_QOS_EFLOWSPEC = WSABASEERR + 1017,
WSA_QOS_EPROVSPECBUF = WSABASEERR + 1018,
WSA_QOS_EFILTERSTYLE = WSABASEERR + 1019,
WSA_QOS_EFILTERTYPE = WSABASEERR + 1020,
WSA_QOS_EFILTERCOUNT = WSABASEERR + 1021,
WSA_QOS_EOBJLENGTH = WSABASEERR + 1022,
WSA_QOS_EFLOWCOUNT = WSABASEERR + 1023,
WSA_QOS_EUNKOWNPSOBJ = WSABASEERR + 1024,
WSA_QOS_EPOLICYOBJ = WSABASEERR + 1025,
WSA_QOS_EFLOWDESC = WSABASEERR + 1026,
WSA_QOS_EPSFLOWSPEC = WSABASEERR + 1027,
WSA_QOS_EPSFILTERSPEC = WSABASEERR + 1028,
WSA_QOS_ESDMODEOBJ = WSABASEERR + 1029,
WSA_QOS_ESHAPERATEOBJ = WSABASEERR + 1030,
WSA_QOS_RESERVED_PETYPE = WSABASEERR + 1031,
}

View File

@ -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/. */
/**
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/sys/windows/package.d,
* tanya/sys/windows/package.d)
*/
module tanya.sys.windows;
version (Windows):
public import tanya.sys.windows.def;
public import tanya.sys.windows.error;
public import tanya.sys.windows.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.
* 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.
* 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.
* 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,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/. */
/**
* Test suite for $(D_KEYWORD unittest)-blocks.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/test/package.d,
* tanya/test/package.d)
*/
module tanya.test;
public import tanya.test.assertion;

109
source/tanya/typecons.d Normal file
View File

@ -0,0 +1,109 @@
/* 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 constructors.
*
* This module contains templates that allow to build new types from the
* available ones.
*
* Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/typecons.d,
* tanya/typecons.d)
*/
module tanya.typecons;
import tanya.meta.metafunction;
/**
* $(D_PSYMBOL Pair) can store two heterogeneous objects.
*
* The objects can by accessed by index as $(D_INLINECODE obj[0]) and
* $(D_INLINECODE obj[1]) or by optional names (e.g.
* $(D_INLINECODE obj.first)).
*
* $(D_PARAM Specs) contains a list of object types and names. First
* comes the object type, then an optional string containing the name.
* If you want the object be accessible only by its index (`0` or `1`),
* just skip the name.
*
* Params:
* Specs = Field types and names.
*/
template Pair(Specs...)
{
template parseSpecs(int fieldCount, Specs...)
{
static if (Specs.length == 0)
{
alias parseSpecs = AliasSeq!();
}
else static if (is(Specs[0]) && fieldCount < 2)
{
static if (is(typeof(Specs[1]) == string))
{
alias parseSpecs
= AliasSeq!(Specs[0],
parseSpecs!(fieldCount + 1, Specs[2 .. $]));
}
else
{
alias parseSpecs
= AliasSeq!(Specs[0],
parseSpecs!(fieldCount + 1, Specs[1 .. $]));
}
}
else
{
static assert(false, "Invalid argument: " ~ Specs[0].stringof);
}
}
struct Pair
{
/// Field types.
alias Types = parseSpecs!(0, Specs);
static assert(Types.length == 2, "Invalid argument count.");
// Create field aliases.
static if (is(typeof(Specs[1]) == string))
{
mixin("alias " ~ Specs[1] ~ " = expand[0];");
}
static if (is(typeof(Specs[2]) == string))
{
mixin("alias " ~ Specs[2] ~ " = expand[1];");
}
else static if (is(typeof(Specs[3]) == string))
{
mixin("alias " ~ Specs[3] ~ " = expand[1];");
}
/// Represents the values of the $(D_PSYMBOL Pair) as a list of values.
Types expand;
alias expand this;
}
}
///
unittest
{
static assert(is(Pair!(int, int)));
static assert(!is(Pair!(int, 5)));
static assert(is(Pair!(int, "first", int)));
static assert(is(Pair!(int, "first", int, "second")));
static assert(is(Pair!(int, "first", int)));
static assert(is(Pair!(int, int, "second")));
static assert(!is(Pair!("first", int, "second", int)));
static assert(!is(Pair!(int, int, int)));
static assert(!is(Pair!(int, "first")));
}