180 Commits

Author SHA1 Message Date
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
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
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
f4b90d8b51 Add string skeleton 2017-02-10 19:22:46 +01:00
56 changed files with 20985 additions and 6096 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

5
.gitignore vendored
View File

@ -1,11 +1,14 @@
# Binary # Binary
*.[oa] *.[oa]
*.exe
# D # D
.dub .dub
__test__*__ __test__*__
__test__*__.core __test__*__.core
/tanya-test-library /tanya-test-*
/docs/ /docs/
/docs.json /docs.json
/*.lst

View File

@ -7,14 +7,28 @@ os:
language: d language: d
d: d:
- dmd-2.076.0
- dmd-2.075.1
- dmd-2.074.1
- dmd-2.073.2 - dmd-2.073.2
- dmd-2.072.2
- dmd-2.071.2
- dmd-2.070.2
env: env:
matrix: matrix:
- ARCH=x86_64 - ARCH=x86_64
- ARCH=x86
addons:
apt:
packages:
- gcc-multilib
before_script:
- if [ "$PS1" = '(dmd-2.076.0)' ]; then
export UNITTEST="unittest-cov";
fi
script: script:
- dub test --arch=$ARCH - 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.

92
CONTRIBUTING.md Normal file
View File

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

173
README.md
View File

@ -1,6 +1,8 @@
# Tanya # Tanya
[![Build Status](https://travis-ci.org/caraus-ecms/tanya.svg?branch=master)](https://travis-ci.org/caraus-ecms/tanya) [![Build status](https://travis-ci.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 version](https://img.shields.io/dub/v/tanya.svg)](https://code.dlang.org/packages/tanya)
[![Dub downloads](https://img.shields.io/dub/dt/tanya.svg)](https://code.dlang.org/packages/tanya) [![Dub downloads](https://img.shields.io/dub/dt/tanya.svg)](https://code.dlang.org/packages/tanya)
[![License](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE) [![License](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
@ -13,54 +15,167 @@ Garbage Collector heap. Everything in the library is usable in @nogc code.
Tanya extends Phobos functionality and provides alternative implementations for Tanya extends Phobos functionality and provides alternative implementations for
data structures and utilities that depend on the Garbage Collector in Phobos. data structures and utilities that depend on the Garbage Collector in Phobos.
* [Bug tracker](https://issues.caraus.io/projects/tanya) * [Bug tracker](https://issues.caraus.io/projects/tanya/issues)
* [Documentation](https://docs.caraus.io/tanya) * [API Documentation](https://docs.caraus.io/tanya)
* [Contribution guidelines](CONTRIBUTING.md)
## Overview ## Overview
Tanya consists of the following packages: Tanya consists of the following packages and (top-level) modules:
* `async`: Event loop (epoll, kqueue and IOCP). * `async`: Event loop (epoll, kqueue and IOCP).
* `container`: Queue, Vector, Singly linked list, buffers. * `container`: Queue, Array, Singly and doubly linked lists, Buffers, UTF-8
string, Hash set.
* `format`: Formatting and conversion functions.
* `math`: Arbitrary precision integer and a set of functions. * `math`: Arbitrary precision integer and a set of functions.
* `memory`: Tools for manual memory management (allocator, reference counting, * `memory`: Tools for manual memory management (allocators, smart pointers).
helper functions). * `meta`: Template metaprogramming. This package contains utilities to acquire
* `network`: URL-Parsing, sockets. 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.
* `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 ### Supported compilers
* dmd 2.073.2 | dmd |
* dmd 2.072.2 |:-------:|
* dmd 2.071.2 | 2.076.0 |
* dmd 2.070.2 | 2.075.1 |
| 2.074.1 |
| 2.073.2 |
### Current status ### Current status
The library is currently under development, but the API is becoming gradually Following modules are under development:
stable.
`container`s are being extended to support ranges. Also following modules are | Feature | Branch | Build status |
coming soon: |----------|:---------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
* UTF-8 string. | 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) |
* Hash table. | 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) |
`math` package contains an arbitrary precision integer implementation that ### Release management
needs more test cases, better performance and some additional features
(constructing from a string and an ubyte array, and converting it back).
### Further characteristics 3-week release cycle.
* Tanya is a native D library. 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 * Tanya is cross-platform. The development happens on a 64-bit Linux, but it
is being tested on Windows and FreeBSD as well. is being tested on Windows and FreeBSD as well.
* The library isn't thread-safe. Thread-safity should be added later. * The library isn't thread-safe yet.
## Contributing
Since I'm mostly busy writing new code and implementing new features I would ## Feedback
appreciate, if anyone uses the library. It would help me to improve the
codebase and fix issues.
Feel free to contact me if you have any questions. Any feedback about your experience with tanya would be greatly appreciated. Feel free to
[contact me](mailto:info@caraus.de).

66
appveyor.yml Normal file
View File

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

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="disabled"
; 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", "name": "tanya",
"description": "General purpose, @nogc library", "description": "General purpose, @nogc library. Containers, networking, metaprogramming, memory management, utilities",
"license": "MPL-2.0", "license": "MPL-2.0",
"copyright": "(c) Eugene Wissner <info@caraus.de>", "copyright": "(c) Eugene Wissner <info@caraus.de>",
"authors": [ "authors": [
@ -11,7 +11,14 @@
"configurations": [ "configurations": [
{ {
"name": "library" "name": "library",
"targetType": "library",
"versions": ["TanyaPhobos"]
},
{
"name": "native",
"targetType": "library",
"platforms": ["linux-x86_64"]
} }
] ]
} }

View File

@ -3,14 +3,21 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Event loop implementation for Linux.
*
* Copyright: Eugene Wissner 2016-2017. * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/event/epoll.d,
* tanya/async/event/epoll.d)
*/ */
module tanya.async.event.epoll; module tanya.async.event.epoll;
version (linux): version (D_Ddoc)
{
}
else version (linux):
public import core.sys.linux.epoll; public import core.sys.linux.epoll;
import tanya.async.protocol; import tanya.async.protocol;
@ -18,7 +25,7 @@ import tanya.async.event.selector;
import tanya.async.loop; import tanya.async.loop;
import tanya.async.transport; import tanya.async.transport;
import tanya.async.watcher; import tanya.async.watcher;
import tanya.container.vector; import tanya.container.array;
import tanya.memory; import tanya.memory;
import tanya.memory.mmappool; import tanya.memory.mmappool;
import tanya.network.socket; import tanya.network.socket;
@ -37,7 +44,7 @@ extern (C) nothrow @nogc
final class EpollLoop : SelectorLoop final class EpollLoop : SelectorLoop
{ {
protected int fd; protected int fd;
private Vector!epoll_event events; private Array!epoll_event events;
/** /**
* Initializes the loop. * Initializes the loop.
@ -49,7 +56,7 @@ final class EpollLoop : SelectorLoop
throw defaultAllocator.make!BadLoopException("epoll initialization failed"); throw defaultAllocator.make!BadLoopException("epoll initialization failed");
} }
super(); super();
events = Vector!epoll_event(maxEvents, MmapPool.instance); events = Array!epoll_event(maxEvents, MmapPool.instance);
} }
/** /**

View File

@ -3,14 +3,21 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Event loop implementation for Windows.
*
* Copyright: Eugene Wissner 2016-2017. * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/event/iocp.d,
* tanya/async/event/iocp.d)
*/ */
module tanya.async.event.iocp; module tanya.async.event.iocp;
version (Windows): version (D_Ddoc)
{
}
else version (Windows):
import tanya.container.buffer; import tanya.container.buffer;
import tanya.async.loop; import tanya.async.loop;
@ -226,8 +233,8 @@ final class IOCPLoop : Loop
return false; return false;
} }
} }
if (!(oldEvents & Event.read) && (events & Event.read) if ((!(oldEvents & Event.read) && (events & Event.read))
|| !(oldEvents & Event.write) && (events & Event.write)) || (!(oldEvents & Event.write) && (events & Event.write)))
{ {
auto transport = cast(StreamTransport) watcher; auto transport = cast(StreamTransport) watcher;
assert(transport !is null); assert(transport !is null);

View File

@ -2,15 +2,22 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /*
* Event loop implementation for *BSD.
*
* Copyright: Eugene Wissner 2016-2017. * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/event/kqueue.d,
* tanya/async/event/kqueue.d)
*/ */
module tanya.async.event.kqueue; module tanya.async.event.kqueue;
version (OSX) version (D_Ddoc)
{
}
else version (OSX)
{ {
version = MacBSD; version = MacBSD;
} }
@ -50,7 +57,7 @@ import tanya.async.event.selector;
import tanya.async.loop; import tanya.async.loop;
import tanya.async.transport; import tanya.async.transport;
import tanya.async.watcher; import tanya.async.watcher;
import tanya.container.vector; import tanya.container.array;
import tanya.memory; import tanya.memory;
import tanya.memory.mmappool; import tanya.memory.mmappool;
import tanya.network.socket; import tanya.network.socket;
@ -116,8 +123,8 @@ extern(C) int kevent(int kq, const kevent_t *changelist, int nchanges,
final class KqueueLoop : SelectorLoop final class KqueueLoop : SelectorLoop
{ {
protected int fd; protected int fd;
private Vector!kevent_t events; private Array!kevent_t events;
private Vector!kevent_t changes; private Array!kevent_t changes;
private size_t changeCount; private size_t changeCount;
/** /**
@ -139,8 +146,8 @@ final class KqueueLoop : SelectorLoop
throw make!BadLoopException(defaultAllocator, throw make!BadLoopException(defaultAllocator,
"kqueue initialization failed"); "kqueue initialization failed");
} }
events = Vector!kevent_t(64, MmapPool.instance); events = Array!kevent_t(64, MmapPool.instance);
changes = Vector!kevent_t(64, MmapPool.instance); changes = Array!kevent_t(64, MmapPool.instance);
} }
/** /**
@ -151,7 +158,7 @@ final class KqueueLoop : SelectorLoop
close(fd); close(fd);
} }
private void set(socket_t socket, short filter, ushort flags) @nogc private void set(SocketType socket, short filter, ushort flags) @nogc
{ {
if (changes.length <= changeCount) if (changes.length <= changeCount)
{ {

View File

@ -2,22 +2,29 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /*
* This module contains base implementations for reactor event loops.
*
* Copyright: Eugene Wissner 2016-2017. * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/event/selector.d,
* tanya/async/event/selector.d)
*/ */
module tanya.async.event.selector; module tanya.async.event.selector;
version (Posix): version (D_Ddoc)
{
}
else version (Posix):
import tanya.async.loop; import tanya.async.loop;
import tanya.async.protocol; import tanya.async.protocol;
import tanya.async.transport; import tanya.async.transport;
import tanya.async.watcher; import tanya.async.watcher;
import tanya.container.buffer; import tanya.container.buffer;
import tanya.container.vector; import tanya.container.array;
import tanya.memory; import tanya.memory;
import tanya.memory.mmappool; import tanya.memory.mmappool;
import tanya.network.socket; import tanya.network.socket;
@ -78,7 +85,8 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
return cast(ConnectedSocket) socket_; return cast(ConnectedSocket) socket_;
} }
private @property void socket(ConnectedSocket socket) pure nothrow @safe @nogc private @property void socket(ConnectedSocket socket)
pure nothrow @safe @nogc
in in
{ {
assert(socket !is null); assert(socket !is null);
@ -133,7 +141,9 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
void close() @nogc void close() @nogc
{ {
closing = true; closing = true;
loop.reify(this, EventMask(Event.read, Event.write), EventMask(Event.write)); loop.reify(this,
EventMask(Event.read, Event.write),
EventMask(Event.write));
} }
/** /**
@ -205,20 +215,20 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
abstract class SelectorLoop : Loop abstract class SelectorLoop : Loop
{ {
/// Pending connections. /// Pending connections.
protected Vector!SocketWatcher connections; protected Array!SocketWatcher connections;
this() @nogc this() @nogc
{ {
super(); super();
connections = Vector!SocketWatcher(maxEvents, MmapPool.instance); connections = Array!SocketWatcher(maxEvents, MmapPool.instance);
} }
~this() @nogc ~this() @nogc
{ {
foreach (ref connection; connections) foreach (ref connection; connections)
{ {
// We want to free only the transports. ConnectionWatcher are created by the // We want to free only the transports. ConnectionWatcher are
// user and should be freed by himself. // created by the user and should be freed by himself.
if (cast(StreamTransport) connection !is null) if (cast(StreamTransport) connection !is null)
{ {
MmapPool.instance.dispose(connection); MmapPool.instance.dispose(connection);

View File

@ -3,14 +3,39 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* This module provides API for Windows I/O Completion Ports.
*
* Note: Available only on Windows.
*
* Copyright: Eugene Wissner 2016-2017. * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/iocp.d,
* tanya/async/iocp.d)
*/ */
module tanya.async.iocp; module tanya.async.iocp;
version (Windows): version (Windows)
{
version = WindowsDoc;
}
else version (D_Ddoc)
{
version = WindowsDoc;
version (Windows)
{
}
else
{
private struct OVERLAPPED
{
}
private alias HANDLE = void*;
}
}
version (WindowsDoc):
import core.sys.windows.winbase; import core.sys.windows.winbase;
import core.sys.windows.windef; import core.sys.windows.windef;

View File

@ -3,10 +3,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Copyright: Eugene Wissner 2016-2017. * Interface for the event loop implementations and the default event loop
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * chooser.
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* *
* --- * ---
* import tanya.async; * import tanya.async;
@ -38,11 +36,11 @@
* *
* version (Windows) * version (Windows)
* { * {
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.INET); * auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.inet);
* } * }
* else * else
* { * {
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.INET); * auto sock = defaultAllocator.make!StreamSocket(AddressFamily.inet);
* sock.blocking = false; * sock.blocking = false;
* } * }
* *
@ -61,6 +59,13 @@
* defaultAllocator.dispose(address); * 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; module tanya.async.loop;
@ -79,6 +84,9 @@ import tanya.network.socket;
version (DisableBackends) version (DisableBackends)
{ {
} }
else version (D_Ddoc)
{
}
else version (linux) else version (linux)
{ {
import tanya.async.event.epoll; import tanya.async.event.epoll;
@ -109,6 +117,30 @@ else version (DragonFlyBSD)
{ {
version = Kqueue; version = Kqueue;
} }
version (unittest)
{
final class TestLoop : Loop
{
override protected bool reify(SocketWatcher watcher,
EventMask oldEvents,
EventMask events) @nogc
{
return true;
}
override protected void poll() @nogc
{
assert(!this.done);
unloop();
}
override protected @property uint maxEvents()
const pure nothrow @safe @nogc
{
return 64U;
}
}
}
/** /**
* Events. * Events.
@ -129,7 +161,7 @@ alias EventMask = BitFlags!Event;
*/ */
abstract class Loop abstract class Loop
{ {
private bool done; private bool done = true;
/// Pending watchers. /// Pending watchers.
protected Queue!Watcher pendings; protected Queue!Watcher pendings;
@ -144,6 +176,14 @@ abstract class Loop
return 128U; return 128U;
} }
private unittest
{
auto loop = defaultAllocator.make!TestLoop;
assert(loop.maxEvents == 64);
defaultAllocator.dispose(loop);
}
/** /**
* Initializes the loop. * Initializes the loop.
*/ */
@ -168,18 +208,18 @@ abstract class Loop
*/ */
void run() @nogc void run() @nogc
{ {
done = false; this.done = false;
do do
{ {
poll(); poll();
// Invoke pendings // Invoke pendings
foreach (ref w; pendings) foreach (ref w; this.pendings)
{ {
w.invoke(); w.invoke();
} }
} }
while (!done); while (!this.done);
} }
/** /**
@ -187,7 +227,32 @@ abstract class Loop
*/ */
void unloop() @safe pure nothrow @nogc 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);
} }
/** /**
@ -266,6 +331,17 @@ abstract class Loop
blockTime_ = blockTime; blockTime_ = blockTime;
} }
private unittest
{
auto loop = defaultAllocator.make!TestLoop;
assert(loop.blockTime == 1.dur!"minutes");
loop.blockTime = 2.dur!"minutes";
assert(loop.blockTime == 2.dur!"minutes");
defaultAllocator.dispose(loop);
}
/** /**
* Does the actual polling. * Does the actual polling.
*/ */
@ -344,3 +420,16 @@ body
} }
private Loop defaultLoop_; private Loop defaultLoop_;
private unittest
{
auto oldLoop = defaultLoop_;
auto loop = defaultAllocator.make!TestLoop;
defaultLoop = loop;
assert(defaultLoop_ is loop);
assert(defaultLoop is loop);
defaultLoop_ = oldLoop;
defaultAllocator.dispose(loop);
}

View File

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

View File

@ -3,10 +3,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* This module contains protocol which handle data in asynchronous
* applications.
*
* When an event from the network arrives, a protocol method gets
* called and can respond to the event.
*
* Copyright: Eugene Wissner 2016-2017. * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/protocol.d,
* tanya/async/protocol.d)
*/ */
module tanya.async.protocol; module tanya.async.protocol;

View File

@ -3,10 +3,15 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* This module contains transports which are responsible for data dilvery
* between two parties of an asynchronous communication.
*
* Copyright: Eugene Wissner 2016-2017. * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/transport.d,
* tanya/async/transport.d)
*/ */
module tanya.async.transport; module tanya.async.transport;

View File

@ -3,10 +3,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Watchers register user's interest in some event.
*
* Copyright: Eugene Wissner 2016-2017. * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/watcher.d,
* tanya/async/watcher.d)
*/ */
module tanya.async.watcher; module tanya.async.watcher;
@ -36,6 +40,19 @@ abstract class Watcher
void invoke() @nogc; void invoke() @nogc;
} }
version (unittest)
{
final class DummyWatcher : Watcher
{
bool invoked;
override void invoke() @nogc
{
this.invoked = true;
}
}
}
/** /**
* Socket watcher. * Socket watcher.
*/ */

View File

@ -3,15 +3,19 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* This module contains buffers designed for C-style input/output APIs.
*
* Copyright: Eugene Wissner 2016-2017. * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/container/buffer.d,
* tanya/container/buffer.d)
*/ */
module tanya.container.buffer; module tanya.container.buffer;
import std.traits;
import tanya.memory; import tanya.memory;
import tanya.meta.trait;
version (unittest) version (unittest)
{ {

View File

@ -9,14 +9,100 @@
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/container/entry.d,
* tanya/container/entry.d)
*/ */
module tanya.container.entry; module tanya.container.entry;
import tanya.meta.trait;
import tanya.typecons;
package struct SEntry(T) package struct SEntry(T)
{ {
/// Item content. // Item content.
T content; T content;
/// Next item. // Next item.
SEntry* next; 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

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

View File

@ -9,14 +9,16 @@
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/container/queue.d,
* tanya/container/queue.d)
*/ */
module tanya.container.queue; module tanya.container.queue;
import core.exception; import core.exception;
import std.traits;
import std.algorithm.mutation; import std.algorithm.mutation;
import tanya.container.entry; import tanya.container.entry;
import tanya.memory; import tanya.memory;
import tanya.meta.trait;
/** /**
* FIFO queue. * FIFO queue.

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 std.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 - 1) == this.lengthIndex)
{
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 std.range.primitives;
static assert(isBidirectionalRange!(Set!int.ConstRange));
static assert(isBidirectionalRange!(Set!int.Range));
static assert(!isInfinite!(Set!int.Range));
static assert(!hasLength!(Set!int.Range));
static assert(is(Set!uint));
static assert(is(Set!long));
static assert(is(Set!ulong));
static assert(is(Set!short));
static assert(is(Set!ushort));
static assert(is(Set!bool));
}

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,691 @@
/* 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;
/**
* 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;
}
}
///
pure nothrow @safe @nogc unittest
{
auto val = 5.to!int();
assert(val == 5);
static assert(is(typeof(val) == int));
}
private pure nothrow @safe @nogc unittest
{
int val = 5;
assert(val.to!int() == 5);
}
/**
* Performs checked conversion from an integral type $(D_PARAM From) to an
* integral type $(D_PARAM To).
*
* 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;
}
}
private pure nothrow @safe @nogc 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);
}
private 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);
}
private unittest
{
ConvException exception;
try
{
assert(int.min.to!short == int.min);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private unittest
{
ConvException exception;
try
{
assert(int.max.to!short == int.max);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private unittest
{
ConvException exception;
try
{
assert(uint.max.to!ushort == ushort.max);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private unittest
{
ConvException exception;
try
{
assert((-1).to!uint == -1);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
enum Test : int
{
one,
two,
}
assert(Test.one.to!int == 0);
assert(Test.two.to!int == 1);
}
/**
* 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 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);
}
private @nogc unittest
{
ConvException exception;
try
{
assert((-1).to!bool);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
ConvException exception;
try
{
assert(2.to!bool);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
/// Ditto.
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 unittest
{
assert("true".to!bool);
assert(!"false".to!bool);
assert(String("true").to!bool);
assert(!String("false").to!bool);
}
private @nogc unittest
{
ConvException exception;
try
{
assert("1".to!bool);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
/**
* Converts a boolean to $(D_PARAM To).
*
* 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)(const From from)
if (is(Unqual!From == bool) && isNumeric!To && !is(Unqual!To == Unqual!From))
{
return from;
}
///
pure nothrow @safe @nogc 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);
}
/// Ditto.
To to(To, From)(const From from)
if (is(Unqual!From == bool) && is(Unqual!To == String))
{
return String(from ? "true" : "false");
}
///
@nogc unittest
{
assert(true.to!String == "true");
assert(false.to!String == "false");
}
private @nogc unittest
{
static assert(is(typeof((const String("true")).to!bool)));
static assert(is(typeof(false.to!(const String) == "false")));
}
/**
* Converts a floating point number to an integral type.
*
* 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 unittest
{
assert(1.5.to!int == 1);
assert(2147483646.5.to!int == 2147483646);
assert((-2147483647.5).to!int == -2147483647);
assert(2147483646.5.to!uint == 2147483646);
}
private @nogc unittest
{
ConvException exception;
try
{
assert(2147483647.5.to!int == 2147483647);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
ConvException exception;
try
{
assert((-2147483648.5).to!int == -2147483648);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
ConvException exception;
try
{
assert((-21474.5).to!uint == -21474);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
/**
* Performs checked conversion from an integral type $(D_PARAM From) to an
* $(D_KEYWORD enum).
*
* Params:
* From = Source type.
* To = Target type.
* from = Source value.
*
* Returns: $(D_KEYWORD enum) value.
*
* Throws: $(D_PSYMBOL ConvException) if $(D_PARAM from) is not a member of
* $(D_PSYMBOL To).
*/
To to(To, From)(From from)
if (isIntegral!From && is(To == enum))
{
foreach (m; EnumMembers!To)
{
if (from == m)
{
return m;
}
}
throw make!ConvException(defaultAllocator,
"Value not found in enum '" ~ To.stringof ~ "'");
}
///
@nogc unittest
{
enum Test : int
{
one,
two,
}
static assert(is(typeof(1.to!Test) == Test));
assert(0.to!Test == Test.one);
assert(1.to!Test == Test.two);
}
private @nogc unittest
{
enum Test : uint
{
one,
two,
}
ConvException exception;
try
{
assert(5.to!Test == Test.one);
}
catch (ConvException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
// Returns the last part of buffer with converted number.
package(tanya) char[] number2String(T)(const T number,
return ref char[21] buffer)
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.
private pure nothrow @system @nogc unittest
{
char[21] buf;
assert(number2String(80, buf) == "80");
assert(number2String(-80, buf) == "-80");
assert(number2String(0, buf) == "0");
assert(number2String(uint.max, buf) == "4294967295");
assert(number2String(int.min, buf) == "-2147483648");
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,16 +3,20 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* This package provides mathematical functions.
*
* Copyright: Eugene Wissner 2016-2017. * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/math/package.d,
* tanya/math/package.d)
*/ */
module tanya.math; module tanya.math;
import std.traits;
public import tanya.math.mp; public import tanya.math.mp;
public import tanya.math.random; public import tanya.math.random;
import tanya.meta.trait;
version (unittest) version (unittest)
{ {
@ -87,22 +91,22 @@ in
} }
body body
{ {
size_t i = y.length; size_t i;
auto tmp1 = Integer(x, x.allocator); auto tmp1 = Integer(x, x.allocator);
auto result = Integer(x.allocator); auto result = Integer(x.allocator);
bool firstBit;
if (x.length == 0 && i != 0) if (x.size == 0 && y.size != 0)
{ {
i = 0; i = y.size;
} }
else else
{ {
result = 1; result = 1;
} }
while (i) while (i < y.size)
{ {
--i; for (uint mask = 0x01; mask != 0x10000000; mask <<= 1)
for (ubyte mask = 0x01; mask; mask <<= 1)
{ {
if (y.rep[i] & mask) if (y.rep[i] & mask)
{ {
@ -113,6 +117,7 @@ body
tmp1 *= tmp2; tmp1 *= tmp2;
tmp1 %= z; tmp1 %= z;
} }
++i;
} }
return result; return result;
} }

View File

@ -9,6 +9,8 @@
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/math/random.d,
* tanya/math/random.d)
*/ */
module tanya.math.random; module tanya.math.random;
@ -54,17 +56,17 @@ abstract class EntropySource
/** /**
* Returns: Minimum bytes required from the entropy source. * Returns: Minimum bytes required from the entropy source.
*/ */
@property immutable(ubyte) threshold() const @safe pure nothrow; @property ubyte threshold() const pure nothrow @safe @nogc;
/** /**
* Returns: Whether this entropy source is strong. * Returns: Whether this entropy source is strong.
*/ */
@property immutable(bool) strong() const @safe pure nothrow; @property bool strong() const pure nothrow @safe @nogc;
/** /**
* Returns: Amount of already generated entropy. * Returns: Amount of already generated entropy.
*/ */
@property ushort size() const @safe pure nothrow @property ushort size() const pure nothrow @safe @nogc
{ {
return size_; return size_;
} }
@ -74,7 +76,7 @@ abstract class EntropySource
* size = Amount of already generated entropy. Cannot be smaller than the * size = Amount of already generated entropy. Cannot be smaller than the
* already set value. * already set value.
*/ */
@property void size(ushort size) @safe pure nothrow @property void size(ushort size) pure nothrow @safe @nogc
{ {
size_ = size; size_ = size;
} }
@ -89,12 +91,12 @@ abstract class EntropySource
* Returns: Number of bytes that were copied to the $(D_PARAM output) * Returns: Number of bytes that were copied to the $(D_PARAM output)
* or $(D_PSYMBOL Nullable!ubyte.init) on error. * or $(D_PSYMBOL Nullable!ubyte.init) on error.
*/ */
Nullable!ubyte poll(out ubyte[maxGather] output); Nullable!ubyte poll(out ubyte[maxGather] output) @nogc;
} }
version (linux) version (linux)
{ {
extern (C) long syscall(long number, ...) nothrow; extern (C) long syscall(long number, ...) nothrow @system @nogc;
/** /**
* Uses getrandom system call. * Uses getrandom system call.
@ -104,7 +106,7 @@ version (linux)
/** /**
* Returns: Minimum bytes required from the entropy source. * Returns: Minimum bytes required from the entropy source.
*/ */
override @property immutable(ubyte) threshold() const @safe pure nothrow override @property ubyte threshold() const pure nothrow @safe @nogc
{ {
return 32; return 32;
} }
@ -112,7 +114,7 @@ version (linux)
/** /**
* Returns: Whether this entropy source is strong. * Returns: Whether this entropy source is strong.
*/ */
override @property immutable(bool) strong() const @safe pure nothrow override @property bool strong() const pure nothrow @safe @nogc
{ {
return true; return true;
} }
@ -127,7 +129,7 @@ version (linux)
* Returns: Number of bytes that were copied to the $(D_PARAM output) * Returns: Number of bytes that were copied to the $(D_PARAM output)
* or $(D_PSYMBOL Nullable!ubyte.init) on error. * or $(D_PSYMBOL Nullable!ubyte.init) on error.
*/ */
override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow @nogc
out (length) out (length)
{ {
assert(length <= maxGather); assert(length <= maxGather);
@ -145,6 +147,18 @@ version (linux)
return ret; return ret;
} }
} }
version (X86_64)
{
private unittest
{
auto entropy = defaultAllocator.make!Entropy();
ubyte[blockSize] output;
output = entropy.random;
defaultAllocator.dispose(entropy);
}
}
} }
/** /**
@ -177,7 +191,8 @@ class Entropy
* allocator = Allocator to allocate entropy sources available on the * allocator = Allocator to allocate entropy sources available on the
* system. * system.
*/ */
this(size_t maxSources = 20, shared Allocator allocator = defaultAllocator) this(const size_t maxSources = 20,
shared Allocator allocator = defaultAllocator) @nogc
in in
{ {
assert(maxSources > 0 && maxSources <= ubyte.max); assert(maxSources > 0 && maxSources <= ubyte.max);
@ -196,7 +211,7 @@ class Entropy
/** /**
* Returns: Amount of the registered entropy sources. * Returns: Amount of the registered entropy sources.
*/ */
@property ubyte sourceCount() const @safe pure nothrow @property ubyte sourceCount() const pure nothrow @safe @nogc
{ {
return sourceCount_; return sourceCount_;
} }
@ -212,7 +227,7 @@ class Entropy
* See_Also: * See_Also:
* $(D_PSYMBOL EntropySource) * $(D_PSYMBOL EntropySource)
*/ */
Entropy opOpAssign(string Op)(EntropySource source) @safe pure nothrow Entropy opOpAssign(string Op)(EntropySource source) pure nothrow @safe @nogc
if (Op == "~") if (Op == "~")
in in
{ {
@ -230,7 +245,7 @@ class Entropy
* Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was * Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was
* registered or it failed. * registered or it failed.
*/ */
@property ubyte[blockSize] random() @property ubyte[blockSize] random() @nogc
in in
{ {
assert(sourceCount_ > 0, "No entropy sources defined."); assert(sourceCount_ > 0, "No entropy sources defined.");
@ -301,7 +316,7 @@ class Entropy
*/ */
protected void update(in ubyte sourceId, protected void update(in ubyte sourceId,
ref ubyte[maxGather] data, ref ubyte[maxGather] data,
ubyte length) @safe pure nothrow ubyte length) pure nothrow @safe @nogc
{ {
ubyte[2] header; ubyte[2] header;

View File

@ -3,10 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* 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. * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/allocator.d,
* tanya/memory/allocator.d)
*/ */
module tanya.memory.allocator; module tanya.memory.allocator;
@ -28,7 +35,7 @@ interface Allocator
* *
* Returns: Pointer to the new allocated memory. * Returns: Pointer to the new allocated memory.
*/ */
void[] allocate(in size_t size) shared nothrow @nogc; void[] allocate(const size_t size) shared pure nothrow @nogc;
/** /**
* Deallocates a memory block. * Deallocates a memory block.
@ -38,7 +45,7 @@ interface Allocator
* *
* Returns: Whether the deallocation was successful. * Returns: Whether the deallocation was successful.
*/ */
bool deallocate(void[] p) shared nothrow @nogc; bool deallocate(void[] p) shared pure nothrow @nogc;
/** /**
* Increases or decreases the size of a memory block. * Increases or decreases the size of a memory block.
@ -49,7 +56,7 @@ interface Allocator
* *
* Returns: Pointer to the allocated memory. * Returns: Pointer to the allocated memory.
*/ */
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc; bool reallocate(ref void[] p, const size_t size) shared pure nothrow @nogc;
/** /**
* Reallocates a memory block in place if possible or returns * Reallocates a memory block in place if possible or returns
@ -63,5 +70,12 @@ interface Allocator
* *
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise. * Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
*/ */
bool reallocateInPlace(ref void[] p, in size_t size) shared nothrow @nogc; bool reallocateInPlace(ref void[] p, const size_t size)
shared pure nothrow @nogc;
}
package template GetPureInstance(T : Allocator)
{
alias GetPureInstance = shared(T) function()
pure nothrow @nogc;
} }

View File

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

View File

@ -3,22 +3,35 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Allocator based on $(D_PSYMBOL malloc), $(D_PSYMBOL realloc) and $(D_PSYMBOL free).
*
* Copyright: Eugene Wissner 2017. * Copyright: Eugene Wissner 2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/mallocator.d,
* tanya/memory/mallocator.d)
*/ */
module tanya.memory.mallocator; module tanya.memory.mallocator;
version (TanyaPhobos):
import core.stdc.stdlib; import core.stdc.stdlib;
import std.algorithm.comparison;
import tanya.memory.allocator; 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.
*/ */
final 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. * Allocates $(D_PARAM size) bytes of memory.
* *
@ -27,13 +40,13 @@ final class Mallocator : Allocator
* *
* Returns: The pointer to the new allocated memory. * Returns: The pointer to the new allocated memory.
*/ */
void[] allocate(in size_t size) shared nothrow @nogc void[] allocate(const size_t size) shared pure nothrow @nogc
{ {
if (!size) if (size == 0)
{ {
return null; return null;
} }
auto p = malloc(size + psize); auto p = (cast(MallocType) &malloc)(size + psize);
return p is null ? null : p[psize .. psize + size]; return p is null ? null : p[psize .. psize + size];
} }
@ -42,10 +55,11 @@ final class Mallocator : Allocator
@nogc nothrow unittest @nogc nothrow unittest
{ {
auto p = Mallocator.instance.allocate(20); auto p = Mallocator.instance.allocate(20);
assert(p.length == 20); assert(p.length == 20);
Mallocator.instance.deallocate(p); Mallocator.instance.deallocate(p);
p = Mallocator.instance.allocate(0);
assert(p.length == 0);
} }
/** /**
@ -56,11 +70,11 @@ final class Mallocator : Allocator
* *
* Returns: Whether the deallocation was successful. * Returns: Whether the deallocation was successful.
*/ */
bool deallocate(void[] p) shared nothrow @nogc bool deallocate(void[] p) shared pure nothrow @nogc
{ {
if (p !is null) if (p !is null)
{ {
free(p.ptr - psize); (cast(FreeType) &free)(p.ptr - psize);
} }
return true; return true;
} }
@ -84,11 +98,19 @@ final class Mallocator : Allocator
* *
* Returns: $(D_KEYWORD false). * Returns: $(D_KEYWORD false).
*/ */
bool reallocateInPlace(ref void[] p, const size_t size) shared nothrow @nogc bool reallocateInPlace(ref void[] p, const size_t size)
shared pure nothrow @nogc
{ {
return false; return false;
} }
///
@nogc nothrow unittest
{
void[] p;
assert(!Mallocator.instance.reallocateInPlace(p, 8));
}
/** /**
* Increases or decreases the size of a memory block. * Increases or decreases the size of a memory block.
* *
@ -98,7 +120,7 @@ final class Mallocator : Allocator
* *
* Returns: Whether the reallocation was successful. * Returns: Whether the reallocation was successful.
*/ */
bool reallocate(ref void[] p, const size_t size) shared nothrow @nogc bool reallocate(ref void[] p, const size_t size) shared pure nothrow @nogc
{ {
if (size == 0) if (size == 0)
{ {
@ -115,7 +137,7 @@ final class Mallocator : Allocator
} }
else else
{ {
auto r = realloc(p.ptr - psize, size + psize); auto r = (cast(ReallocType) &realloc)(p.ptr - psize, size + psize);
if (r !is null) if (r !is null)
{ {
@ -144,24 +166,34 @@ final class Mallocator : Allocator
assert(p is null); 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. * Returns: The alignment offered.
*/ */
@property uint alignment() shared const pure nothrow @safe @nogc @property uint alignment() shared const pure nothrow @safe @nogc
{ {
return cast(uint) max(double.alignof, real.alignof); return (void*).alignof;
} }
/** private nothrow @nogc unittest
* Static allocator instance and initializer. {
* assert(Mallocator.instance.alignment == (void*).alignof);
* Returns: The global $(D_PSYMBOL Allocator) instance. }
*/
static @property ref shared(Mallocator) instance() @nogc nothrow static private shared(Mallocator) instantiate() nothrow @nogc
{ {
if (instance_ is null) if (instance_ is null)
{ {
immutable size = __traits(classInstanceSize, Mallocator) + psize; const size = __traits(classInstanceSize, Mallocator) + psize;
void* p = malloc(size); void* p = malloc(size);
if (p !is null) if (p !is null)
@ -173,13 +205,23 @@ final class Mallocator : Allocator
return instance_; return instance_;
} }
/**
* Static allocator instance and initializer.
*
* Returns: The global $(D_PSYMBOL Allocator) instance.
*/
static @property shared(Mallocator) instance() pure nothrow @nogc
{
return (cast(GetPureInstance!Mallocator) &instantiate)();
}
/// ///
@nogc nothrow unittest @nogc nothrow unittest
{ {
assert(instance is instance); assert(instance is instance);
} }
private enum psize = 8; private enum ushort psize = 8;
private shared static Mallocator instance_; private shared static Mallocator instance_;
} }

View File

@ -3,10 +3,14 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Native allocator for Posix and Windows.
*
* Copyright: Eugene Wissner 2016-2017. * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/mmappool.d,
* tanya/memory/mmappool.d)
*/ */
module tanya.memory.mmappool; module tanya.memory.mmappool;
@ -16,14 +20,63 @@ import tanya.memory.allocator;
version (Posix) version (Posix)
{ {
import core.stdc.errno; import core.sys.posix.sys.mman : PROT_READ, PROT_WRITE, MAP_PRIVATE,
import core.sys.posix.sys.mman; MAP_ANON, MAP_FAILED;
import core.sys.posix.unistd; 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) else version (Windows)
{ {
import core.sys.windows.winbase; import core.sys.windows.winbase : GetSystemInfo, SYSTEM_INFO;
import core.sys.windows.windows;
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;
}
} }
/** /**
@ -50,7 +103,9 @@ else version (Windows)
*/ */
final class MmapPool : Allocator final class MmapPool : Allocator
{ {
invariant version (none)
{
pure nothrow @nogc invariant
{ {
for (auto r = &head; *r !is null; r = &((*r).next)) for (auto r = &head; *r !is null; r = &((*r).next))
{ {
@ -64,6 +119,7 @@ final class MmapPool : Allocator
while ((block = block.next) !is null); while ((block = block.next) !is null);
} }
} }
}
/** /**
* Allocates $(D_PARAM size) bytes of memory. * Allocates $(D_PARAM size) bytes of memory.
@ -73,13 +129,17 @@ final class MmapPool : Allocator
* *
* Returns: Pointer to the new allocated memory. * Returns: Pointer to the new allocated memory.
*/ */
void[] allocate(in size_t size) shared nothrow @nogc void[] allocate(const size_t size) shared pure nothrow @nogc
{ {
if (!size) if (size == 0)
{
return null;
}
const dataSize = addAlignment(size);
if (dataSize < size)
{ {
return null; return null;
} }
immutable dataSize = addAlignment(size);
void* data = findBlock(dataSize); void* data = findBlock(dataSize);
if (data is null) if (data is null)
@ -94,13 +154,32 @@ final class MmapPool : Allocator
nothrow unittest nothrow unittest
{ {
auto p = MmapPool.instance.allocate(20); auto p = MmapPool.instance.allocate(20);
assert(p); assert(p);
MmapPool.instance.deallocate(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 * Search for a block large enough to keep $(D_PARAM size) and split it
* into two blocks if the block is too large. * into two blocks if the block is too large.
* *
@ -109,7 +188,7 @@ final class MmapPool : Allocator
* *
* Returns: Data the block points to or $(D_KEYWORD null). * Returns: Data the block points to or $(D_KEYWORD null).
*/ */
private void* findBlock(in ref size_t size) shared nothrow @nogc private void* findBlock(const ref size_t size) shared pure nothrow @nogc
{ {
Block block1; Block block1;
RegionLoop: for (auto r = head; r !is null; r = r.next) RegionLoop: for (auto r = head; r !is null; r = r.next)
@ -169,7 +248,7 @@ final class MmapPool : Allocator
* *
* Returns: Whether the deallocation was successful. * Returns: Whether the deallocation was successful.
*/ */
bool deallocate(void[] p) shared nothrow @nogc bool deallocate(void[] p) shared pure nothrow @nogc
{ {
if (p.ptr is null) if (p.ptr is null)
{ {
@ -191,14 +270,7 @@ final class MmapPool : Allocator
{ {
block.region.next.prev = block.region.prev; block.region.next.prev = block.region.prev;
} }
version (Posix) return unmapMemory(block.region, block.region.size);
{
return munmap(cast(void*) block.region, block.region.size) == 0;
}
version (Windows)
{
return VirtualFree(cast(void*) block.region, 0, MEM_RELEASE) == 0;
}
} }
// Merge blocks if neigbours are free. // Merge blocks if neigbours are free.
if (block.next !is null && block.next.free) if (block.next !is null && block.next.free)
@ -242,7 +314,8 @@ final class MmapPool : Allocator
* *
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise. * Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
*/ */
bool reallocateInPlace(ref void[] p, in size_t size) shared nothrow @nogc bool reallocateInPlace(ref void[] p, const size_t size)
shared pure nothrow @nogc
{ {
if (p is null || size == 0) if (p is null || size == 0)
{ {
@ -258,19 +331,25 @@ final class MmapPool : Allocator
if (block1.size >= size) if (block1.size >= size)
{ {
// Enough space in the current block. Can happen because of the alignment. // Enough space in the current block.
p = p.ptr[0 .. size]; p = p.ptr[0 .. size];
return true; return true;
} }
immutable dataSize = addAlignment(size); const dataSize = addAlignment(size);
immutable delta = dataSize - addAlignment(p.length); const pAlignment = addAlignment(p.length);
assert(pAlignment >= p.length, "Invalid memory chunk length");
const delta = dataSize - pAlignment;
if (block1.next is null if (block1.next is null
|| !block1.next.free || !block1.next.free
|| dataSize < size
|| block1.next.size + BlockEntry.sizeof < delta) || block1.next.size + BlockEntry.sizeof < delta)
{ {
// It is the last block in the region or the next block is too small or not /* * It is the last block in the region
// free. * * The next block is too small
* * The next block isn't free
* * Requested size is too large
*/
return false; return false;
} }
if (block1.next.size >= delta + alignment_) if (block1.next.size >= delta + alignment_)
@ -333,7 +412,7 @@ final class MmapPool : Allocator
* *
* Returns: Whether the reallocation was successful. * Returns: Whether the reallocation was successful.
*/ */
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc bool reallocate(ref void[] p, const size_t size) shared pure nothrow @nogc
{ {
if (size == 0) if (size == 0)
{ {
@ -394,19 +473,14 @@ final class MmapPool : Allocator
MmapPool.instance.deallocate(p); MmapPool.instance.deallocate(p);
} }
/** static private shared(MmapPool) instantiate() nothrow @nogc
* Static allocator instance and initializer.
*
* Returns: Global $(D_PSYMBOL MmapPool) instance.
*/
static @property ref shared(MmapPool) instance() nothrow @nogc
{ {
if (instance_ is null) if (instance_ is null)
{ {
// Get system dependend page size. // Get system dependend page size.
version (Posix) version (Posix)
{ {
pageSize = sysconf(_SC_PAGE_SIZE); size_t pageSize = sysconf(_SC_PAGE_SIZE);
if (pageSize < 65536) if (pageSize < 65536)
{ {
pageSize = pageSize * 65536 / pageSize; pageSize = pageSize * 65536 / pageSize;
@ -416,23 +490,35 @@ final class MmapPool : Allocator
{ {
SYSTEM_INFO si; SYSTEM_INFO si;
GetSystemInfo(&si); GetSystemInfo(&si);
pageSize = si.dwPageSize; size_t pageSize = si.dwPageSize;
} }
immutable instanceSize = addAlignment(__traits(classInstanceSize, MmapPool)); const instanceSize = addAlignment(__traits(classInstanceSize,
MmapPool));
Region head; // Will become soon our region list head Region head; // Will become soon our region list head
void* data = initializeRegion(instanceSize, head); void* data = initializeRegion(instanceSize, head, pageSize);
if (data !is null) if (data !is null)
{ {
memcpy(data, typeid(MmapPool).initializer.ptr, instanceSize); memcpy(data, typeid(MmapPool).initializer.ptr, instanceSize);
instance_ = cast(shared MmapPool) data; instance_ = cast(shared MmapPool) data;
instance_.head = head; instance_.head = head;
instance_.pageSize = pageSize;
} }
} }
return instance_; 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)();
}
/// ///
nothrow unittest nothrow unittest
{ {
@ -448,35 +534,22 @@ final class MmapPool : Allocator
* *
* Returns: A pointer to the data. * Returns: A pointer to the data.
*/ */
private static void* initializeRegion(size_t size, ref Region head) private static void* initializeRegion(const size_t size,
nothrow @nogc ref Region head,
const size_t pageSize)
pure nothrow @nogc
{ {
immutable regionSize = calculateRegionSize(size); const regionSize = calculateRegionSize(size, pageSize);
if (regionSize < size)
version (Posix)
{
void* p = mmap(null,
regionSize,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0);
if (p is MAP_FAILED)
{ {
return null; return null;
} }
}
else version (Windows) void* p = mapMemory(regionSize);
{
void* p = VirtualAlloc(null,
regionSize,
MEM_COMMIT,
PAGE_READWRITE);
if (p is null) if (p is null)
{ {
return null; return null;
} }
}
Region region = cast(Region) p; Region region = cast(Region) p;
region.blocks = 1; region.blocks = 1;
@ -513,9 +586,9 @@ final class MmapPool : Allocator
return data; return data;
} }
private void* initializeRegion(size_t size) shared nothrow @nogc private void* initializeRegion(const size_t size) shared pure nothrow @nogc
{ {
return initializeRegion(size, head); return initializeRegion(size, this.head, this.pageSize);
} }
/* /*
@ -524,13 +597,7 @@ final class MmapPool : Allocator
* *
* Returns: Aligned size of $(D_PARAM x). * Returns: Aligned size of $(D_PARAM x).
*/ */
private static immutable(size_t) addAlignment(size_t x) private static size_t addAlignment(const size_t x) pure nothrow @safe @nogc
pure nothrow @safe @nogc
out (result)
{
assert(result > 0);
}
body
{ {
return (x - 1) / alignment_ * alignment_ + alignment_; return (x - 1) / alignment_ * alignment_ + alignment_;
} }
@ -538,19 +605,16 @@ final class MmapPool : Allocator
/* /*
* Params: * Params:
* x = Required space. * x = Required space.
* pageSize = Page size.
* *
* Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)). * Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)).
*/ */
private static immutable(size_t) calculateRegionSize(size_t x) private static size_t calculateRegionSize(ref const size_t x,
nothrow @safe @nogc ref const size_t pageSize)
out (result) pure nothrow @safe @nogc
{ {
assert(result > 0); return (x + RegionEntry.sizeof + BlockEntry.sizeof * 2)
} / pageSize * pageSize + pageSize;
body
{
x += RegionEntry.sizeof + BlockEntry.sizeof * 2;
return x / pageSize * pageSize + pageSize;
} }
/** /**
@ -560,10 +624,16 @@ final class MmapPool : Allocator
{ {
return alignment_; 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 MmapPool instance_;
private shared static size_t pageSize; private shared size_t pageSize;
private shared struct RegionEntry private shared struct RegionEntry
{ {
@ -588,7 +658,7 @@ final class MmapPool : Allocator
// A lot of allocations/deallocations, but it is the minimum caused a // A lot of allocations/deallocations, but it is the minimum caused a
// segmentation fault because MmapPool reallocateInPlace moves a block wrong. // segmentation fault because MmapPool reallocateInPlace moves a block wrong.
unittest private @nogc unittest
{ {
auto a = MmapPool.instance.allocate(16); auto a = MmapPool.instance.allocate(16);
auto d = MmapPool.instance.allocate(16); auto d = MmapPool.instance.allocate(16);

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

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

View File

@ -3,17 +3,25 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Dynamic memory management.
*
* Copyright: Eugene Wissner 2016-2017. * Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/memory/package.d,
* tanya/memory/package.d)
*/ */
module tanya.memory; module tanya.memory;
import core.exception; import core.exception;
public import std.experimental.allocator : make; import std.algorithm.iteration;
import std.traits; import std.algorithm.mutation;
import std.conv;
import std.range;
public import tanya.memory.allocator; public import tanya.memory.allocator;
import tanya.memory.mmappool;
import tanya.meta.trait;
/** /**
* The mixin generates common methods for classes and structs using * The mixin generates common methods for classes and structs using
@ -32,7 +40,7 @@ mixin template DefaultAllocator()
* *
* Precondition: $(D_INLINECODE allocator_ !is null) * Precondition: $(D_INLINECODE allocator_ !is null)
*/ */
this(shared Allocator allocator) this(shared Allocator allocator) pure nothrow @safe @nogc
in in
{ {
assert(allocator !is null); assert(allocator !is null);
@ -50,7 +58,7 @@ mixin template DefaultAllocator()
* *
* Postcondition: $(D_INLINECODE allocator !is null) * Postcondition: $(D_INLINECODE allocator !is null)
*/ */
protected @property shared(Allocator) allocator() nothrow @safe @nogc protected @property shared(Allocator) allocator() pure nothrow @safe @nogc
out (allocator) out (allocator)
{ {
assert(allocator !is null); assert(allocator !is null);
@ -65,7 +73,7 @@ mixin template DefaultAllocator()
} }
/// Ditto. /// Ditto.
@property shared(Allocator) allocator() const nothrow @trusted @nogc @property shared(Allocator) allocator() const pure nothrow @trusted @nogc
out (allocator) out (allocator)
{ {
assert(allocator !is null); assert(allocator !is null);
@ -81,26 +89,44 @@ mixin template DefaultAllocator()
} }
// From druntime // From druntime
private extern (C) void _d_monitordelete(Object h, bool det) nothrow @nogc; extern (C)
private void _d_monitordelete(Object h, bool det) pure nothrow @nogc;
shared Allocator allocator; shared Allocator allocator;
shared static this() nothrow @trusted @nogc shared static this() nothrow @nogc
{ {
import tanya.memory.mallocator; allocator = MmapPool.instance;
allocator = Mallocator.instance;
} }
@property ref shared(Allocator) defaultAllocator() nothrow @safe @nogc 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) out (allocator)
{ {
assert(allocator !is null); assert(allocator !is null);
} }
body body
{ {
return allocator; 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 @property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc
in in
{ {
@ -143,7 +169,7 @@ pure nothrow @safe @nogc
return (size - 1) / alignment * alignment + alignment; return (size - 1) / alignment * alignment + alignment;
} }
/** /*
* Internal function used to create, resize or destroy a dynamic array. It * Internal function used to create, resize or destroy a dynamic array. It
* may throw $(D_PSYMBOL OutOfMemoryError). The new * may throw $(D_PSYMBOL OutOfMemoryError). The new
* allocated part of the array isn't initialized. This function can be trusted * allocated part of the array isn't initialized. This function can be trusted
@ -202,33 +228,25 @@ private unittest
assert(p is null); assert(p is null);
} }
/** /*
* Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T). * Destroys the object.
* It is assumed the respective entities had been allocated with the same * Returns the memory should be freed.
* 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) package(tanya) void[] finalize(T)(ref T* p)
{ {
static if (hasElaborateDestructor!T) static if (hasElaborateDestructor!T)
{ {
destroy(*p); destroy(*p);
} }
() @trusted { allocator.deallocate((cast(void*) p)[0 .. T.sizeof]); }(); return (cast(void*) p)[0 .. T.sizeof];
p = null;
} }
/// Ditto. package(tanya) void[] finalize(T)(ref T p)
void dispose(T)(shared Allocator allocator, auto ref T p)
if (is(T == class) || is(T == interface)) if (is(T == class) || is(T == interface))
{ {
if (p is null) if (p is null)
{ {
return; return null;
} }
static if (is(T == interface)) static if (is(T == interface))
{ {
@ -245,18 +263,12 @@ void dispose(T)(shared Allocator allocator, auto ref T p)
alias ob = p; alias ob = p;
} }
auto ptr = cast(void*) ob; auto ptr = cast(void*) ob;
auto support = ptr[0 .. typeid(ob).initializer.length]; auto support = ptr[0 .. typeid(ob).initializer.length];
scope (success)
{
() @trusted { allocator.deallocate(support); }();
p = null;
}
auto ppv = cast(void**) ptr; auto ppv = cast(void**) ptr;
if (!*ppv) if (!*ppv)
{ {
return; return null;
} }
auto pc = cast(ClassInfo*) *ppv; auto pc = cast(ClassInfo*) *ppv;
scope (exit) scope (exit)
@ -271,7 +283,8 @@ void dispose(T)(shared Allocator allocator, auto ref T p)
// shouldn't throw and if it does, it is an error anyway. // shouldn't throw and if it does, it is an error anyway.
if (c.destructor) if (c.destructor)
{ {
(cast(void function (Object) nothrow @safe @nogc) c.destructor)(ob); alias DtorType = void function(Object) pure nothrow @safe @nogc;
(cast(DtorType) c.destructor)(ob);
} }
} }
while ((c = c.base) !is null); while ((c = c.base) !is null);
@ -280,21 +293,35 @@ void dispose(T)(shared Allocator allocator, auto ref T p)
{ {
_d_monitordelete(cast(Object) ptr, true); _d_monitordelete(cast(Object) ptr, true);
} }
return support;
} }
/// Ditto. package(tanya) void[] finalize(T)(ref T[] p)
void dispose(T)(shared Allocator allocator, auto ref T[] p)
{ {
static if (hasElaborateDestructor!(typeof(p[0]))) static if (hasElaborateDestructor!(typeof(p[0])))
{ {
import std.algorithm.iteration; p.each!((ref e) => destroy(e));
p.each!(e => destroy(e));
} }
() @trusted { allocator.deallocate(p); }(); return p;
}
/**
* Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T).
* It is assumed the respective entities had been allocated with the same
* allocator.
*
* 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; p = null;
} }
unittest private unittest
{ {
struct S struct S
{ {
@ -306,3 +333,138 @@ unittest
defaultAllocator.dispose(p); 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); }();
}
auto ptr = (() @trusted => (cast(T*) mem[0 .. stateSize!T].ptr))();
return emplace!T(ptr, args);
}
///
unittest
{
int* i = defaultAllocator.make!int(5);
assert(*i == 5);
defaultAllocator.dispose(i);
}
/**
* Constructs a new array with $(D_PARAM n) elements.
*
* Params:
* T = Array type.
* allocator = Allocator.
* n = Array size.
*
* Returns: Newly created array.
*
* Precondition: $(D_INLINECODE allocator !is null
* && n <= size_t.max / ElementType!T.sizeof)
*/
T make(T)(shared Allocator allocator, const size_t n)
if (isArray!T)
in
{
assert(allocator !is null);
assert(n <= size_t.max / ElementType!T.sizeof);
}
body
{
auto ret = allocator.resize!(ElementType!T)(null, n);
ret.uninitializedFill(ElementType!T.init);
return ret;
}
///
unittest
{
int[] i = defaultAllocator.make!(int[])(2);
assert(i.length == 2);
assert(i[0] == int.init && i[1] == int.init);
defaultAllocator.dispose(i);
}

View File

@ -0,0 +1,925 @@
/* 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, array) to manage its lifetime.
*
* 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 core.exception;
import std.algorithm.comparison;
import std.algorithm.mutation;
import std.conv;
import std.range;
import tanya.memory;
import tanya.meta.trait;
private template Payload(T)
{
static if (is(T == class) || is(T == interface) || 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;
}
private @nogc unittest
{
auto rc = defaultAllocator.refCounted!int(5);
rc = defaultAllocator.make!int(7);
assert(*rc == 7);
}
/// 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;
}
private @nogc unittest
{
RefCounted!int rc;
assert(!rc.isInitialized);
rc = null;
assert(!rc.isInitialized);
}
/// 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;
}
///
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);
}
private @nogc unittest
{
auto rc = defaultAllocator.refCounted!int(5);
void func(RefCounted!int param) @nogc
{
assert(param.count == 2);
param = defaultAllocator.make!int(7);
assert(param.count == 1);
assert(*param == 7);
}
func(rc);
assert(rc.count == 1);
assert(*rc == 5);
}
private @nogc unittest
{
RefCounted!int rc;
void func(RefCounted!int param) @nogc
{
assert(param.count == 0);
param = defaultAllocator.make!int(7);
assert(param.count == 1);
assert(*param == 7);
}
func(rc);
assert(rc.count == 0);
}
private unittest
{
RefCounted!int rc1, rc2;
static assert(is(typeof(rc1 = rc2)));
}
version (unittest)
{
private class A
{
uint *destroyed;
this(ref uint destroyed) @nogc
{
this.destroyed = &destroyed;
}
~this() @nogc
{
++(*destroyed);
}
}
private struct B
{
int prop;
@disable this();
this(int param1) @nogc
{
prop = param1;
}
}
}
private @nogc unittest
{
uint destroyed;
auto a = defaultAllocator.make!A(destroyed);
assert(destroyed == 0);
{
auto rc = RefCounted!A(a, defaultAllocator);
assert(rc.count == 1);
void func(RefCounted!A rc) @nogc
{
assert(rc.count == 2);
}
func(rc);
assert(rc.count == 1);
}
assert(destroyed == 1);
RefCounted!int rc;
assert(rc.count == 0);
rc = defaultAllocator.make!int(8);
assert(rc.count == 1);
}
private @nogc unittest
{
auto rc = RefCounted!int(defaultAllocator);
assert(!rc.isInitialized);
assert(rc.allocator is defaultAllocator);
}
private @nogc unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(rc.count == 1);
void func(RefCounted!int rc) @nogc
{
assert(rc.count == 2);
rc = null;
assert(!rc.isInitialized);
assert(rc.count == 0);
}
assert(rc.count == 1);
func(rc);
assert(rc.count == 1);
rc = null;
assert(!rc.isInitialized);
assert(rc.count == 0);
}
private unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(*rc == 5);
void func(RefCounted!int rc) @nogc
{
assert(rc.count == 2);
rc = defaultAllocator.refCounted!int(4);
assert(*rc == 4);
assert(rc.count == 1);
}
func(rc);
assert(*rc == 5);
}
private unittest
{
static assert(is(typeof(RefCounted!int.storage.payload) == int*));
static assert(is(typeof(RefCounted!A.storage.payload) == A));
static assert(is(RefCounted!B));
static assert(is(RefCounted!A));
}
/**
* 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]);
static if (is(T == class))
{
rc.storage.payload = emplace!T(mem[storageSize .. $], args);
}
else
{
auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
rc.storage.payload = emplace!T(ptr, args);
}
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);
}
///
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);
}
private @nogc unittest
{
struct E
{
}
auto b = defaultAllocator.refCounted!B(15);
static assert(is(typeof(b.storage.payload) == B*));
static assert(is(typeof(b.prop) == int));
static assert(!is(typeof(defaultAllocator.refCounted!B())));
static assert(is(typeof(defaultAllocator.refCounted!E())));
static assert(!is(typeof(defaultAllocator.refCounted!E(5))));
{
auto rc = defaultAllocator.refCounted!B(3);
assert(rc.get().prop == 3);
}
{
auto rc = defaultAllocator.refCounted!E();
assert(rc.count);
}
}
private @nogc unittest
{
auto rc = defaultAllocator.refCounted!(int[])(5);
assert(rc.length == 5);
}
private @nogc unittest
{
auto p1 = defaultAllocator.make!int(5);
auto p2 = p1;
auto rc = RefCounted!int(p1, defaultAllocator);
assert(rc.get() is p2);
}
private @nogc unittest
{
static bool destroyed = false;
struct F
{
~this() @nogc
{
destroyed = true;
}
}
{
auto rc = defaultAllocator.refCounted!F();
}
assert(destroyed);
}
/**
* $(D_PSYMBOL Unique) stores an object that gets destroyed at the end of its scope.
*
* 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 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 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 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 unittest
{
auto p = defaultAllocator.make!int(5);
auto s = Unique!int(p, defaultAllocator);
assert(*s == 5);
}
///
@nogc unittest
{
static bool destroyed = false;
struct F
{
~this() @nogc
{
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);
}
private unittest
{
static assert(is(typeof(defaultAllocator.unique!B(5))));
static assert(is(typeof(defaultAllocator.unique!(int[])(5))));
}
private unittest
{
auto s = defaultAllocator.unique!int(5);
assert(*s == 5);
s = null;
assert(s is null);
}
private unittest
{
auto s = defaultAllocator.unique!int(5);
assert(*s == 5);
s = defaultAllocator.unique!int(4);
assert(*s == 4);
}
private @nogc unittest
{
auto p1 = defaultAllocator.make!int(5);
auto p2 = p1;
auto rc = Unique!int(p1, defaultAllocator);
assert(rc.get() is p2);
}
private @nogc unittest
{
auto rc = Unique!int(defaultAllocator);
assert(rc.allocator is defaultAllocator);
}

View File

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

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;

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,741 @@
/* 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;
version (TanyaPhobos)
{
public import std.traits : Unqual,
OriginalType,
CopyConstness,
CopyTypeQualifiers,
Unsigned,
Signed,
PointerTarget,
KeyType,
ValueType,
Promoted,
InoutOf,
ConstOf,
SharedOf,
SharedInoutOf,
SharedConstOf,
ImmutableOf,
QualifierOf;
}
else:
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;
}
}
///
pure nothrow @safe @nogc 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;
}
}
///
pure nothrow @safe @nogc 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;
}
}
///
pure nothrow @safe @nogc 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;
}
}
///
pure nothrow @safe @nogc unittest
{
static assert(is(CopyTypeQualifiers!(int, char) == char));
static assert(is(CopyTypeQualifiers!(const int, char) == const char));
static assert(is(CopyTypeQualifiers!(immutable int, char) == immutable char));
static assert(is(CopyTypeQualifiers!(inout int, char) == inout char));
static assert(is(CopyTypeQualifiers!(inout const int, char) == inout const char));
static assert(is(CopyTypeQualifiers!(shared int, char) == shared char));
static assert(is(CopyTypeQualifiers!(shared const int, char) == shared const char));
static assert(is(CopyTypeQualifiers!(shared inout int, char) == shared inout char));
static assert(is(CopyTypeQualifiers!(shared inout const int, char) == shared inout const char));
}
/**
* Evaluates to the unsigned counterpart of the integral type $(D_PARAM T) preserving all type qualifiers.
* If $(D_PARAM T) is already unsigned, $(D_INLINECODE Unsigned!T) aliases $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: Unsigned counterpart of $(D_PARAM T).
*
* See_Also: $(D_PSYMBOL isSigned).
*/
template Unsigned(T)
if (isIntegral!T)
{
alias UnqualedType = Unqual!(OriginalType!T);
static if (is(UnqualedType == byte))
{
alias Unsigned = CopyTypeQualifiers!(T, ubyte);
}
else static if (is(UnqualedType == short))
{
alias Unsigned = CopyTypeQualifiers!(T, ushort);
}
else static if (is(UnqualedType == int))
{
alias Unsigned = CopyTypeQualifiers!(T, uint);
}
else static if (is(UnqualedType == long))
{
alias Unsigned = CopyTypeQualifiers!(T, ulong);
}
else
{
alias Unsigned = T;
}
}
///
pure nothrow @safe @nogc unittest
{
static assert(is(Unsigned!byte == ubyte));
static assert(is(Unsigned!short == ushort));
static assert(is(Unsigned!int == uint));
static assert(is(Unsigned!long == ulong));
static assert(is(Unsigned!(const byte) == const ubyte));
static assert(is(Unsigned!(shared byte) == shared ubyte));
static assert(is(Unsigned!(shared const byte) == shared const ubyte));
static assert(!is(Unsigned!float));
static assert(is(Unsigned!ubyte == ubyte));
}
/**
* Evaluates to the signed counterpart of the integral type $(D_PARAM T) preserving all type qualifiers.
* If $(D_PARAM T) is already signed, $(D_INLINECODE Signed!T) aliases $(D_PARAM T).
*
* Params:
* T = A type.
*
* Returns: Signed counterpart of $(D_PARAM T).
*
* See_Also: $(D_PSYMBOL isUnsigned).
*/
template Signed(T)
if (isIntegral!T)
{
alias UnqualedType = Unqual!(OriginalType!T);
static if (is(UnqualedType == ubyte))
{
alias Signed = CopyTypeQualifiers!(T, byte);
}
else static if (is(UnqualedType == ushort))
{
alias Signed = CopyTypeQualifiers!(T, short);
}
else static if (is(UnqualedType == uint))
{
alias Signed = CopyTypeQualifiers!(T, int);
}
else static if (is(UnqualedType == ulong))
{
alias Signed = CopyTypeQualifiers!(T, long);
}
else
{
alias Signed = T;
}
}
///
pure nothrow @safe @nogc unittest
{
static assert(is(Signed!ubyte == byte));
static assert(is(Signed!ushort == short));
static assert(is(Signed!uint == int));
static assert(is(Signed!ulong == long));
static assert(is(Signed!(const ubyte) == const byte));
static assert(is(Signed!(shared ubyte) == shared byte));
static assert(is(Signed!(shared const ubyte) == shared const byte));
static assert(!is(Signed!float));
static assert(is(Signed!byte == byte));
}
/**
* Retrieves the target type `U` of a pointer `U*`.
*
* 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");
}
}
///
pure nothrow @safe @nogc 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");
}
}
///
pure nothrow @safe @nogc 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");
}
}
///
pure nothrow @safe @nogc 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));
}
///
pure nothrow @safe @nogc 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);
///
pure nothrow @safe @nogc 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);
///
pure nothrow @safe @nogc 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);
///
pure nothrow @safe @nogc 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);
///
pure nothrow @safe @nogc 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);
///
pure nothrow @safe @nogc 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);
///
pure nothrow @safe @nogc 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);
///
pure nothrow @safe @nogc 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);
///
pure nothrow @safe @nogc 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;
}
}
///
pure nothrow @safe @nogc 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);
}
///
pure nothrow @safe @nogc 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 std.range.primitives;
import tanya.meta.trait;
import tanya.meta.transform;
/**
* 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;

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

@ -0,0 +1,575 @@
/* 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 std.ascii : isAlphaNum, isDigit;
import std.uni : isAlpha, isNumber;
import tanya.memory;
/**
* 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.isNumber &&
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 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");
}
private @nogc unittest
{
auto u = URL("127.0.0.1");
assert(u.path == "127.0.0.1");
u = URL("http://127.0.0.1");
assert(u.scheme == "http");
assert(u.host == "127.0.0.1");
u = URL("http://127.0.0.1:9000");
assert(u.scheme == "http");
assert(u.host == "127.0.0.1");
assert(u.port == 9000);
u = URL("127.0.0.1:80");
assert(u.host == "127.0.0.1");
assert(u.port == 80);
assert(u.path is null);
u = URL("//example.net");
assert(u.host == "example.net");
assert(u.scheme is null);
u = URL("//example.net?q=before:after");
assert(u.host == "example.net");
assert(u.query == "q=before:after");
u = URL("localhost:8080");
assert(u.host == "localhost");
assert(u.port == 8080);
assert(u.path is null);
u = URL("ftp:");
assert(u.scheme == "ftp");
u = URL("file:///C:\\Users");
assert(u.scheme == "file");
assert(u.path == "C:\\Users");
u = URL("localhost:66000");
assert(u.scheme == "localhost");
assert(u.path == "66000");
u = URL("file:///home/");
assert(u.scheme == "file");
assert(u.path == "/home/");
u = URL("file:///home/?q=asdf");
assert(u.scheme == "file");
assert(u.path == "/home/");
assert(u.query == "q=asdf");
u = URL("http://secret@example.org");
assert(u.scheme == "http");
assert(u.host == "example.org");
assert(u.user == "secret");
u = URL("h_tp://:80");
assert(u.path == "h_tp://:80");
assert(u.port == 0);
u = URL("zlib:/home/user/file.gz");
assert(u.scheme == "zlib");
assert(u.path == "/home/user/file.gz");
u = URL("h_tp:asdf");
assert(u.path == "h_tp:asdf");
}
private @nogc unittest
{
URIException exception;
try
{
auto u = URL("http://:80");
}
catch (URIException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
URIException exception;
try
{
auto u = URL(":80");
}
catch (URIException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
URIException exception;
try
{
auto u = URL("http://user1:pass1@user2:pass2@example.org");
}
catch (URIException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
URIException exception;
try
{
auto u = URL("http://blah.com:port");
}
catch (URIException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
private @nogc unittest
{
URIException exception;
try
{
auto u = URL("http://blah.com:66000");
}
catch (URIException e)
{
exception = e;
}
assert(exception !is null);
defaultAllocator.dispose(exception);
}
// Issue 254: https://issues.caraus.io/issues/254.
private @system @nogc unittest
{
auto u = URL("ftp://");
assert(u.scheme == "ftp");
}
/**
* Attempts to parse an URL from a string and returns the specified component
* of the URL or $(D_PSYMBOL URL) if no component is specified.
*
* 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
{
return URL(source);
}
///
@nogc unittest
{
auto u = parseURL("http://example.org:5326");
assert(u.scheme == parseURL!"scheme"("http://example.org:5326"));
assert(u.host == parseURL!"host"("http://example.org:5326"));
assert(u.user == parseURL!"user"("http://example.org:5326"));
assert(u.pass == parseURL!"pass"("http://example.org:5326"));
assert(u.path == parseURL!"path"("http://example.org:5326"));
assert(u.query == parseURL!"query"("http://example.org:5326"));
assert(u.fragment == parseURL!"fragment"("http://example.org:5326"));
assert(u.port == parseURL!"port"("http://example.org:5326"));
}

View File

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

View File

@ -3,22 +3,27 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Copyright: Eugene Wissner 2016. * Low-level socket programming.
*
* Copyright: Eugene Wissner 2016-2017.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/network/socket.d,
* tanya/network/socket.d)
*/ */
module tanya.network.socket; module tanya.network.socket;
import tanya.memory;
import core.stdc.errno; import core.stdc.errno;
import core.time; import core.time;
import std.algorithm.comparison; import std.algorithm.comparison;
import std.algorithm.searching; public import std.socket : SocketOptionLevel, SocketOption;
public import std.socket : socket_t, Linger, SocketOptionLevel, SocketOption,
SocketType, AddressFamily, AddressInfo;
import std.traits; import std.traits;
import std.typecons; import std.typecons;
import tanya.memory;
/// Value returned by socket operations on error.
enum int socketError = -1;
version (Posix) version (Posix)
{ {
@ -30,7 +35,12 @@ version (Posix)
import core.sys.posix.sys.time; import core.sys.posix.sys.time;
import core.sys.posix.unistd; import core.sys.posix.unistd;
private enum SOCKET_ERROR = -1; enum SocketType : int
{
init = -1,
}
private alias LingerField = int;
} }
else version (Windows) else version (Windows)
{ {
@ -41,16 +51,23 @@ else version (Windows)
import core.sys.windows.windef; import core.sys.windows.windef;
import core.sys.windows.winsock2; import core.sys.windows.winsock2;
enum SocketType : size_t
{
init = ~0,
}
private alias LingerField = ushort;
enum : uint enum : uint
{ {
IOC_UNIX = 0x00000000, IOC_UNIX = 0x00000000,
IOC_WS2 = 0x08000000, IOC_WS2 = 0x08000000,
IOC_PROTOCOL = 0x10000000, IOC_PROTOCOL = 0x10000000,
IOC_VOID = 0x20000000, /// No parameters. IOC_VOID = 0x20000000, // No parameters.
IOC_OUT = 0x40000000, /// Copy parameters back. IOC_OUT = 0x40000000, // Copy parameters back.
IOC_IN = 0x80000000, /// Copy parameters into. IOC_IN = 0x80000000, // Copy parameters into.
IOC_VENDOR = 0x18000000, IOC_VENDOR = 0x18000000,
IOC_INOUT = (IOC_IN | IOC_OUT), /// Copy parameter into and get back. IOC_INOUT = (IOC_IN | IOC_OUT), // Copy parameter into and get back.
} }
template _WSAIO(int x, int y) template _WSAIO(int x, int y)
@ -181,19 +198,26 @@ else version (Windows)
LPINT, LPINT,
SOCKADDR**, SOCKADDR**,
LPINT); LPINT);
const GUID WSAID_GETACCEPTEXSOCKADDRS = {0xb5367df2,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]}; const GUID WSAID_GETACCEPTEXSOCKADDRS = {
0xb5367df2, 0xcbac, 0x11cf,
[ 0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92 ],
};
struct WSABUF { struct WSABUF
{
ULONG len; ULONG len;
CHAR* buf; CHAR* buf;
} }
alias WSABUF* LPWSABUF; alias WSABUF* LPWSABUF;
struct WSAOVERLAPPED { struct WSAOVERLAPPED
{
ULONG_PTR Internal; ULONG_PTR Internal;
ULONG_PTR InternalHigh; ULONG_PTR InternalHigh;
union { union
struct { {
struct
{
DWORD Offset; DWORD Offset;
DWORD OffsetHigh; DWORD OffsetHigh;
} }
@ -217,36 +241,13 @@ else version (Windows)
private WSABUF buffer; private WSABUF buffer;
} }
/**
* Socket returned if a connection has been established.
*/
class OverlappedConnectedSocket : ConnectedSocket class OverlappedConnectedSocket : ConnectedSocket
{ {
/** this(SocketType handle, AddressFamily af) @nogc
* Create a socket.
*
* Params:
* handle = Socket handle.
* af = Address family.
*/
this(socket_t handle, AddressFamily af) @nogc
{ {
super(handle, af); super(handle, af);
} }
/**
* Begins to asynchronously receive data from a connected socket.
*
* Params:
* buffer = Storage location for the received data.
* flags = Flags.
* overlapped = Unique operation identifier.
*
* Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
* $(D_KEYWORD false) otherwise.
*
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
*/
bool beginReceive(ubyte[] buffer, bool beginReceive(ubyte[] buffer,
SocketState overlapped, SocketState overlapped,
Flags flags = Flags(Flag.none)) @nogc @trusted Flags flags = Flags(Flag.none)) @nogc @trusted
@ -266,23 +267,13 @@ else version (Windows)
&overlapped.overlapped, &overlapped.overlapped,
NULL); NULL);
if (result == SOCKET_ERROR && !wouldHaveBlocked) if (result == socketError && !wouldHaveBlocked)
{ {
throw defaultAllocator.make!SocketException("Unable to receive"); throw defaultAllocator.make!SocketException("Unable to receive");
} }
return result == 0; return result == 0;
} }
/**
* Ends a pending asynchronous read.
*
* Params
* overlapped = Unique operation identifier.
*
* Returns: Number of bytes received.
*
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
*/
int endReceive(SocketState overlapped) @nogc @trusted int endReceive(SocketState overlapped) @nogc @trusted
out (count) out (count)
{ {
@ -307,19 +298,6 @@ else version (Windows)
return lpNumber; return lpNumber;
} }
/**
* Sends data asynchronously to a connected socket.
*
* Params:
* buffer = Data to be sent.
* flags = Flags.
* overlapped = Unique operation identifier.
*
* Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
* $(D_KEYWORD false) otherwise.
*
* Throws: $(D_PSYMBOL SocketException) if unable to send.
*/
bool beginSend(ubyte[] buffer, bool beginSend(ubyte[] buffer,
SocketState overlapped, SocketState overlapped,
Flags flags = Flags(Flag.none)) @nogc @trusted Flags flags = Flags(Flag.none)) @nogc @trusted
@ -337,7 +315,7 @@ else version (Windows)
&overlapped.overlapped, &overlapped.overlapped,
NULL); NULL);
if (result == SOCKET_ERROR && !wouldHaveBlocked) if (result == socketError && !wouldHaveBlocked)
{ {
disconnected_ = true; disconnected_ = true;
throw defaultAllocator.make!SocketException("Unable to send"); throw defaultAllocator.make!SocketException("Unable to send");
@ -345,16 +323,6 @@ else version (Windows)
return result == 0; return result == 0;
} }
/**
* Ends a pending asynchronous send.
*
* Params
* overlapped = Unique operation identifier.
*
* Returns: Number of bytes sent.
*
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
*/
int endSend(SocketState overlapped) @nogc @trusted int endSend(SocketState overlapped) @nogc @trusted
out (count) out (count)
{ {
@ -378,17 +346,9 @@ else version (Windows)
class OverlappedStreamSocket : StreamSocket class OverlappedStreamSocket : StreamSocket
{ {
/// Accept extension function pointer. // Accept extension function pointer.
package LPFN_ACCEPTEX acceptExtension; package LPFN_ACCEPTEX acceptExtension;
/**
* Create a socket.
*
* Params:
* af = Address family.
*
* Throws: $(D_PSYMBOL SocketException) on errors.
*/
this(AddressFamily af) @nogc @trusted this(AddressFamily af) @nogc @trusted
{ {
super(af); super(af);
@ -410,28 +370,17 @@ else version (Windows)
&dwBytes, &dwBytes,
NULL, NULL,
NULL); NULL);
if (!result == SOCKET_ERROR) if (!result == socketError)
{ {
throw make!SocketException(defaultAllocator, throw make!SocketException(defaultAllocator,
"Unable to retrieve an accept extension function pointer"); "Unable to retrieve an accept extension function pointer");
} }
} }
/**
* Begins an asynchronous operation to accept an incoming connection attempt.
*
* Params:
* overlapped = Unique operation identifier.
*
* Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
* $(D_KEYWORD false) otherwise.
*
* Throws: $(D_PSYMBOL SocketException) on accept errors.
*/
bool beginAccept(SocketState overlapped) @nogc @trusted bool beginAccept(SocketState overlapped) @nogc @trusted
{ {
auto socket = cast(socket_t) socket(addressFamily, SOCK_STREAM, 0); auto socket = cast(SocketType) socket(addressFamily, 1, 0);
if (socket == socket_t.init) if (socket == SocketType.init)
{ {
throw defaultAllocator.make!SocketException("Unable to create socket"); throw defaultAllocator.make!SocketException("Unable to create socket");
} }
@ -443,7 +392,7 @@ else version (Windows)
overlapped.handle = cast(HANDLE) socket; overlapped.handle = cast(HANDLE) socket;
overlapped.event = OverlappedSocketEvent.accept; overlapped.event = OverlappedSocketEvent.accept;
immutable len = (sockaddr_in.sizeof + 16) * 2; const len = (sockaddr_in.sizeof + 16) * 2;
overlapped.buffer.len = len; overlapped.buffer.len = len;
overlapped.buffer.buf = cast(char*) defaultAllocator.allocate(len).ptr; overlapped.buffer.buf = cast(char*) defaultAllocator.allocate(len).ptr;
@ -463,6 +412,139 @@ else version (Windows)
return result == TRUE; return result == TRUE;
} }
OverlappedConnectedSocket endAccept(SocketState overlapped)
@nogc @trusted
{
scope (exit)
{
defaultAllocator.dispose(overlapped.buffer.buf[0 .. overlapped.buffer.len]);
}
auto socket = make!OverlappedConnectedSocket(defaultAllocator,
cast(SocketType) overlapped.handle,
addressFamily);
scope (failure)
{
defaultAllocator.dispose(socket);
}
socket.setOption(SocketOptionLevel.SOCKET,
cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT,
cast(size_t) handle);
return socket;
}
}
}
else version (D_Ddoc)
{
/// Native socket representation type.
enum SocketType;
/**
* Socket returned if a connection has been established.
*
* Note: Available only on Windows.
*/
class OverlappedConnectedSocket : ConnectedSocket
{
/**
* Create a socket.
*
* Params:
* handle = Socket handle.
* af = Address family.
*/
this(SocketType handle, AddressFamily af) @nogc;
/**
* Begins to asynchronously receive data from a connected socket.
*
* Params:
* buffer = Storage location for the received data.
* flags = Flags.
* overlapped = Unique operation identifier.
*
* Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
* $(D_KEYWORD false) otherwise.
*
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
*/
bool beginReceive(ubyte[] buffer,
SocketState overlapped,
Flags flags = Flags(Flag.none)) @nogc @trusted;
/**
* Ends a pending asynchronous read.
*
* Params:
* overlapped = Unique operation identifier.
*
* Returns: Number of bytes received.
*
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
*
* Postcondition: $(D_INLINECODE result >= 0).
*/
int endReceive(SocketState overlapped) @nogc @trusted;
/**
* Sends data asynchronously to a connected socket.
*
* Params:
* buffer = Data to be sent.
* flags = Flags.
* overlapped = Unique operation identifier.
*
* Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
* $(D_KEYWORD false) otherwise.
*
* Throws: $(D_PSYMBOL SocketException) if unable to send.
*/
bool beginSend(ubyte[] buffer,
SocketState overlapped,
Flags flags = Flags(Flag.none)) @nogc @trusted;
/**
* Ends a pending asynchronous send.
*
* Params:
* overlapped = Unique operation identifier.
*
* Returns: Number of bytes sent.
*
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
*
* Postcondition: $(D_INLINECODE result >= 0).
*/
int endSend(SocketState overlapped) @nogc @trusted;
}
/**
* Windows stream socket overlapped I/O.
*/
class OverlappedStreamSocket : StreamSocket
{
/**
* Create a socket.
*
* Params:
* af = Address family.
*
* Throws: $(D_PSYMBOL SocketException) on errors.
*/
this(AddressFamily af) @nogc @trusted;
/**
* Begins an asynchronous operation to accept an incoming connection attempt.
*
* Params:
* overlapped = Unique operation identifier.
*
* Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
* $(D_KEYWORD false) otherwise.
*
* Throws: $(D_PSYMBOL SocketException) on accept errors.
*/
bool beginAccept(SocketState overlapped) @nogc @trusted;
/** /**
* Asynchronously accepts an incoming connection attempt and creates a * Asynchronously accepts an incoming connection attempt and creates a
* new socket to handle remote host communication. * new socket to handle remote host communication.
@ -474,24 +556,116 @@ else version (Windows)
* *
* Throws: $(D_PSYMBOL SocketException) if unable to accept. * Throws: $(D_PSYMBOL SocketException) if unable to accept.
*/ */
OverlappedConnectedSocket endAccept(SocketState overlapped) @nogc @trusted OverlappedConnectedSocket endAccept(SocketState overlapped)
{ @nogc @trusted;
scope (exit)
{
defaultAllocator.dispose(overlapped.buffer.buf[0 .. overlapped.buffer.len]);
} }
auto socket = make!OverlappedConnectedSocket(defaultAllocator, }
cast(socket_t) overlapped.handle,
addressFamily); /**
scope (failure) * Socket option that specifies what should happen when the socket that
* promises reliable delivery still has untransmitted messages when
* it is closed.
*/
struct Linger
{ {
defaultAllocator.dispose(socket); /// If nonzero, $(D_PSYMBOL close) and $(D_PSYMBOL shutdown) block until
/// the data are transmitted or the timeout period has expired.
LingerField l_onoff;
/// Time, in seconds to wait before any buffered data to be sent is
/// discarded.
LingerField l_linger;
/**
* If $(D_PARAM timeout) is `0`, linger is disabled, otherwise enables the
* linger and sets the timeout.
*
* Params:
* timeout = Timeout, in seconds.
*/
this(const ushort timeout)
{
time = timeout;
} }
socket.setOption(SocketOptionLevel.SOCKET,
cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT, ///
cast(size_t) handle); unittest
return socket; {
{
auto linger = Linger(5);
assert(linger.enabled);
assert(linger.time == 5);
} }
{
auto linger = Linger(0);
assert(!linger.enabled);
}
{ // Default constructor.
Linger linger;
assert(!linger.enabled);
}
}
/**
* System dependent constructor.
*
* Params:
* l_onoff = $(D_PSYMBOL l_onoff) value.
* l_linger = $(D_PSYMBOL l_linger) value.
*/
this(LingerField l_onoff, LingerField l_linger)
{
this.l_onoff = l_onoff;
this.l_linger = l_linger;
}
///
unittest
{
auto linger = Linger(1, 5);
assert(linger.l_onoff == 1);
assert(linger.l_linger == 5);
}
/**
* Params:
* value = Whether to linger after the socket is closed.
*
* See_Also: $(D_PSYMBOL time).
*/
@property enabled(const bool value) pure nothrow @safe @nogc
{
this.l_onoff = value;
}
/**
* Returns: Whether to linger after the socket is closed.
*/
@property bool enabled() const pure nothrow @safe @nogc
{
return this.l_onoff != 0;
}
/**
* Returns: Timeout period, in seconds, to wait before closing the socket
* if the $(D_PSYMBOL Linger) is $(D_PSYMBOL enabled).
*/
@property ushort time() const pure nothrow @safe @nogc
{
return this.l_linger & ushort.max;
}
/**
* Sets timeout period, to wait before closing the socket if the
* $(D_PSYMBOL Linger) is $(D_PSYMBOL enabled), ignored otherwise.
*
* Params:
* timeout = Timeout period, in seconds.
*/
@property void time(const ushort timeout) pure nothrow @safe @nogc
{
this.l_onoff = timeout > 0;
this.l_linger = timeout;
} }
} }
@ -523,7 +697,7 @@ else version (DragonFlyBSD)
version (MacBSD) version (MacBSD)
{ {
enum ESOCKTNOSUPPORT = 44; /// Socket type not suppoted. enum ESOCKTNOSUPPORT = 44; // Socket type not suppoted.
} }
private immutable private immutable
@ -550,12 +724,32 @@ shared static this()
} }
} }
/**
* $(D_PSYMBOL AddressFamily) specifies a communication domain; this selects
* the protocol family which will be used for communication.
*/
enum AddressFamily : int
{
unspec = 0, /// Unspecified.
local = 1, /// Local to host (pipes and file-domain).
unix = local, /// POSIX name for PF_LOCAL.
inet = 2, /// IP protocol family.
ax25 = 3, /// Amateur Radio AX.25.
ipx = 4, /// Novell Internet Protocol.
appletalk = 5, /// Appletalk DDP.
netrom = 6, /// Amateur radio NetROM.
bridge = 7, /// Multiprotocol bridge.
atmpvc = 8, /// ATM PVCs.
x25 = 9, /// Reserved for X.25 project.
inet6 = 10, /// IP version 6.
}
/** /**
* Error codes for $(D_PSYMBOL Socket). * Error codes for $(D_PSYMBOL Socket).
*/ */
enum SocketError : int enum SocketError : int
{ {
/// Unknown error /// Unknown error.
unknown = 0, unknown = 0,
/// Firewall rules forbid connection. /// Firewall rules forbid connection.
accessDenied = EPERM, accessDenied = EPERM,
@ -585,12 +779,12 @@ enum SocketError : int
/** /**
* $(D_PSYMBOL SocketException) should be thrown only if one of the socket functions * $(D_PSYMBOL SocketException) should be thrown only if one of the socket functions
* returns -1 or $(D_PSYMBOL SOCKET_ERROR) and sets $(D_PSYMBOL errno), * $(D_PSYMBOL socketError) and sets $(D_PSYMBOL errno), because
* because $(D_PSYMBOL SocketException) relies on the $(D_PSYMBOL errno) value. * $(D_PSYMBOL SocketException) relies on the $(D_PSYMBOL errno) value.
*/ */
class SocketException : Exception class SocketException : Exception
{ {
immutable SocketError error = SocketError.unknown; const SocketError error = SocketError.unknown;
/** /**
* Params: * Params:
@ -678,16 +872,16 @@ abstract class Socket
} }
/// Socket handle. /// Socket handle.
protected socket_t handle_; protected SocketType handle_;
/// Address family. /// Address family.
protected AddressFamily family; protected AddressFamily family;
private @property void handle(socket_t handle) @nogc private @property void handle(SocketType handle) @nogc
in in
{ {
assert(handle != socket_t.init); assert(handle != SocketType.init);
assert(handle_ == socket_t.init, "Socket handle cannot be changed"); assert(handle_ == SocketType.init, "Socket handle cannot be changed");
} }
body body
{ {
@ -701,7 +895,7 @@ abstract class Socket
} }
} }
@property inout(socket_t) handle() inout const pure nothrow @safe @nogc @property inout(SocketType) handle() inout const pure nothrow @safe @nogc
{ {
return handle_; return handle_;
} }
@ -713,10 +907,10 @@ abstract class Socket
* handle = Socket. * handle = Socket.
* af = Address family. * af = Address family.
*/ */
this(socket_t handle, AddressFamily af) @nogc this(SocketType handle, AddressFamily af) @nogc
in in
{ {
assert(handle != socket_t.init); assert(handle != SocketType.init);
} }
body body
{ {
@ -757,7 +951,7 @@ abstract class Socket
cast(int) level, cast(int) level,
cast(int) option, cast(int) option,
result.ptr, result.ptr,
&length) == SOCKET_ERROR) &length) == socketError)
{ {
throw defaultAllocator.make!SocketException("Unable to get socket option"); throw defaultAllocator.make!SocketException("Unable to get socket option");
} }
@ -777,7 +971,7 @@ abstract class Socket
SocketOption option, SocketOption option,
out Linger result) const @trusted @nogc out Linger result) const @trusted @nogc
{ {
return getOption(level, option, (&result.clinger)[0..1]); return getOption(level, option, (&result)[0 .. 1]);
} }
/// Ditto. /// Ditto.
@ -824,7 +1018,7 @@ abstract class Socket
cast(int)level, cast(int)level,
cast(int)option, cast(int)option,
value.ptr, value.ptr,
cast(uint) value.length) == SOCKET_ERROR) cast(uint) value.length) == socketError)
{ {
throw defaultAllocator.make!SocketException("Unable to set socket option"); throw defaultAllocator.make!SocketException("Unable to set socket option");
} }
@ -841,7 +1035,7 @@ abstract class Socket
void setOption(SocketOptionLevel level, SocketOption option, Linger value) void setOption(SocketOptionLevel level, SocketOption option, Linger value)
const @trusted @nogc const @trusted @nogc
{ {
setOption(level, option, (&value.clinger)[0..1]); setOption(level, option, (&value)[0 .. 1]);
} }
/// Ditto. /// Ditto.
@ -876,7 +1070,7 @@ abstract class Socket
} }
else version (Windows) else version (Windows)
{ {
return blocking_; return this.blocking_;
} }
} }
@ -890,12 +1084,12 @@ abstract class Socket
{ {
int fl = fcntl(handle_, F_GETFL, 0); int fl = fcntl(handle_, F_GETFL, 0);
if (fl != SOCKET_ERROR) if (fl != socketError)
{ {
fl = yes ? fl & ~O_NONBLOCK : fl | O_NONBLOCK; fl = yes ? fl & ~O_NONBLOCK : fl | O_NONBLOCK;
fl = fcntl(handle_, F_SETFL, fl); fl = fcntl(handle_, F_SETFL, fl);
} }
if (fl == SOCKET_ERROR) if (fl == socketError)
{ {
throw make!SocketException(defaultAllocator, throw make!SocketException(defaultAllocator,
"Unable to set socket blocking"); "Unable to set socket blocking");
@ -904,12 +1098,12 @@ abstract class Socket
else version (Windows) else version (Windows)
{ {
uint num = !yes; uint num = !yes;
if (ioctlsocket(handle_, FIONBIO, &num) == SOCKET_ERROR) if (ioctlsocket(handle_, FIONBIO, &num) == socketError)
{ {
throw make!SocketException(defaultAllocator, throw make!SocketException(defaultAllocator,
"Unable to set socket blocking"); "Unable to set socket blocking");
} }
blocking_ = yes; this.blocking_ = yes;
} }
} }
@ -961,7 +1155,7 @@ abstract class Socket
{ {
.close(handle_); .close(handle_);
} }
handle_ = socket_t.init; handle_ = SocketType.init;
} }
/** /**
@ -974,7 +1168,7 @@ abstract class Socket
*/ */
void listen(int backlog) const @trusted @nogc void listen(int backlog) const @trusted @nogc
{ {
if (.listen(handle_, backlog) == SOCKET_ERROR) if (.listen(handle_, backlog) == socketError)
{ {
throw defaultAllocator.make!SocketException("Unable to listen on socket"); throw defaultAllocator.make!SocketException("Unable to listen on socket");
} }
@ -1004,10 +1198,14 @@ interface ConnectionOrientedSocket
*/ */
enum Flag : int enum Flag : int
{ {
none = 0, /// No flags specified /// No flags specified.
outOfBand = MSG_OOB, /// Out-of-band stream data none = 0,
peek = MSG_PEEK, /// Peek at incoming data without removing it from the queue, only for receiving /// Out-of-band stream data.
dontRoute = MSG_DONTROUTE, /// Data should not be subject to routing; this flag may be ignored. Only for sending outOfBand = MSG_OOB,
/// Peek at incoming data without removing it from the queue, only for receiving.
peek = MSG_PEEK,
/// Data should not be subject to routing; this flag may be ignored. Only for sending.
dontRoute = MSG_DONTROUTE,
} }
alias Flags = BitFlags!Flag; alias Flags = BitFlags!Flag;
@ -1023,8 +1221,8 @@ class StreamSocket : Socket, ConnectionOrientedSocket
*/ */
this(AddressFamily af) @trusted @nogc this(AddressFamily af) @trusted @nogc
{ {
auto handle = cast(socket_t) socket(af, SOCK_STREAM, 0); auto handle = cast(SocketType) socket(af, 1, 0);
if (handle == socket_t.init) if (handle == SocketType.init)
{ {
throw defaultAllocator.make!SocketException("Unable to create socket"); throw defaultAllocator.make!SocketException("Unable to create socket");
} }
@ -1041,7 +1239,7 @@ class StreamSocket : Socket, ConnectionOrientedSocket
*/ */
void bind(Address address) const @trusted @nogc void bind(Address address) const @trusted @nogc
{ {
if (.bind(handle_, address.name, address.length) == SOCKET_ERROR) if (.bind(handle_, address.name, address.length) == socketError)
{ {
throw defaultAllocator.make!SocketException("Unable to bind socket"); throw defaultAllocator.make!SocketException("Unable to bind socket");
} }
@ -1060,7 +1258,7 @@ class StreamSocket : Socket, ConnectionOrientedSocket
*/ */
ConnectedSocket accept() @trusted @nogc ConnectedSocket accept() @trusted @nogc
{ {
socket_t sock; SocketType sock;
version (linux) version (linux)
{ {
@ -1069,14 +1267,14 @@ class StreamSocket : Socket, ConnectionOrientedSocket
{ {
flags |= SOCK_NONBLOCK; flags |= SOCK_NONBLOCK;
} }
sock = cast(socket_t).accept4(handle_, null, null, flags); sock = cast(SocketType).accept4(handle_, null, null, flags);
} }
else else
{ {
sock = cast(socket_t).accept(handle_, null, null); sock = cast(SocketType).accept(handle_, null, null);
} }
if (sock == socket_t.init) if (sock == SocketType.init)
{ {
if (wouldHaveBlocked()) if (wouldHaveBlocked())
{ {
@ -1141,7 +1339,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
* handle = Socket. * handle = Socket.
* af = Address family. * af = Address family.
*/ */
this(socket_t handle, AddressFamily af) @nogc this(SocketType handle, AddressFamily af) @nogc
{ {
super(handle, af); super(handle, af);
} }
@ -1190,7 +1388,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
{ {
disconnected_ = true; disconnected_ = true;
} }
else if (ret == SOCKET_ERROR) else if (ret == socketError)
{ {
if (wouldHaveBlocked()) if (wouldHaveBlocked())
{ {
@ -1227,7 +1425,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
} }
sent = .send(handle_, buf.ptr, capToMaxBuffer(buf.length), sendFlags); sent = .send(handle_, buf.ptr, capToMaxBuffer(buf.length), sendFlags);
if (sent != SOCKET_ERROR) if (sent != socketError)
{ {
return sent; return sent;
} }
@ -1267,14 +1465,11 @@ class InternetAddress : Address
/// Internal internet address representation. /// Internal internet address representation.
protected sockaddr_storage storage; protected sockaddr_storage storage;
} }
immutable ushort port_; const ushort port_;
enum enum ushort anyPort = 0;
{
anyPort = 0,
}
this(in string host, ushort port = anyPort) @nogc this(string host, const ushort port = anyPort) @nogc
{ {
if (getaddrinfoPointer is null || freeaddrinfoPointer is null) if (getaddrinfoPointer is null || freeaddrinfoPointer is null)
{ {
@ -1282,7 +1477,7 @@ class InternetAddress : Address
"Address info lookup is not available on this system"); "Address info lookup is not available on this system");
} }
addrinfo* ai_res; addrinfo* ai_res;
port_ = port; this.port_ = port;
// Make C-string from host. // Make C-string from host.
auto node = cast(char[]) allocator.allocate(host.length + 1); auto node = cast(char[]) allocator.allocate(host.length + 1);
@ -1298,16 +1493,17 @@ class InternetAddress : Address
const(char)* servicePointer; const(char)* servicePointer;
if (port) if (port)
{ {
ushort originalPort = port;
ushort start; ushort start;
for (ushort j = 10, i = 4; i > 0; j *= 10, --i) for (ushort j = 10, i = 4; i > 0; j *= 10, --i)
{ {
ushort rest = port % 10; ushort rest = originalPort % 10;
if (rest != 0) if (rest != 0)
{ {
service[i] = cast(char) (rest + '0'); service[i] = cast(char) (rest + '0');
start = i; start = i;
} }
port /= 10; originalPort /= 10;
} }
servicePointer = service[start .. $].ptr; servicePointer = service[start .. $].ptr;
} }
@ -1327,12 +1523,23 @@ class InternetAddress : Address
{ {
*dp = *sp; *dp = *sp;
} }
if (ai_res.ai_family != AddressFamily.INET && ai_res.ai_family != AddressFamily.INET6) if (ai_res.ai_family != AddressFamily.inet && ai_res.ai_family != AddressFamily.inet6)
{ {
throw defaultAllocator.make!SocketException("Wrong address family"); throw defaultAllocator.make!SocketException("Wrong address family");
} }
} }
///
unittest
{
auto address = defaultAllocator.make!InternetAddress("127.0.0.1");
assert(address.port == InternetAddress.anyPort);
assert(address.name !is null);
assert(address.family == AddressFamily.inet);
defaultAllocator.dispose(address);
}
/** /**
* Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure. * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.
*/ */
@ -1349,9 +1556,9 @@ class InternetAddress : Address
// FreeBSD wants to know the exact length of the address on bind. // FreeBSD wants to know the exact length of the address on bind.
switch (family) switch (family)
{ {
case AddressFamily.INET: case AddressFamily.inet:
return sockaddr_in.sizeof; return sockaddr_in.sizeof;
case AddressFamily.INET6: case AddressFamily.inet6:
return sockaddr_in6.sizeof; return sockaddr_in6.sizeof;
default: default:
assert(false); assert(false);
@ -1370,6 +1577,15 @@ class InternetAddress : Address
{ {
return port_; return port_;
} }
///
unittest
{
auto address = defaultAllocator.make!InternetAddress("127.0.0.1",
cast(ushort) 1234);
assert(address.port == 1234);
defaultAllocator.dispose(address);
}
} }
/** /**

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;

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];
}
///
pure nothrow @safe @nogc 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];
}
///
pure nothrow @safe @nogc 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];
}
///
pure nothrow @safe @nogc 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;
}
///
pure nothrow @safe @nogc 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;
}
///
pure nothrow @safe @nogc 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,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 contains generic function 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;

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")));
}