├── dub.selections.json
├── .gitmodules
├── .gitignore
├── makefile
├── .editorconfig
├── index.d
├── dub.json
├── README.md
├── doc
├── custom.css
├── run_examples_custom.js
├── Makefile
└── custom.ddoc
├── .codecov.yml
├── .travis.yml
├── LICENSE.txt
└── source
└── stdx
└── collections
├── package.d
├── common.d
├── rcstring.d
├── hashtable.d
├── slist.d
└── dlist.d
/dub.selections.json:
--------------------------------------------------------------------------------
1 | {
2 | "fileVersion": 1,
3 | "versions": {
4 | "phobos": "~master"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "doc/dlang.org"]
2 | path = doc/dlang.org
3 | url = https://github.com/dlang/dlang.org
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .dub
2 | collections-test-library
3 | .generated
4 | web
5 | *.html
6 | bin/
7 | TODO
8 | libcollections.a
9 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | DMD=dmd
2 |
3 | FILES:=$(wildcard source/stdx/collections/*d)
4 | DFLAGS=
5 |
6 | test:
7 | $(DMD) -ofbin/unittest -Isource -unittest -main $(DFLAGS) $(FILES)
8 | ./bin/unittest
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.{c,h,d,di,dd,sh}]
4 | end_of_line = lf
5 | insert_final_newline = true
6 | indent_style = space
7 | indent_size = 4
8 | trim_trailing_whitespace = true
9 | charset = utf-8
10 |
--------------------------------------------------------------------------------
/index.d:
--------------------------------------------------------------------------------
1 | Ddoc
2 |
3 | $(P Collections.)
4 |
5 | $(P Blabla)
6 |
7 | $(BOOKTABLE ,
8 | $(TR
9 | $(TH Modules)
10 | $(TH Description)
11 | )
12 | $(TR $(TDNW $(MREF stdx,collections,array)★) $(TD Arrays))
13 | )
14 |
15 | Macros:
16 | TITLE=Collections
17 | DDOC_BLANKLINE=
18 | _=
19 |
--------------------------------------------------------------------------------
/dub.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "collections",
3 | "authors": [
4 | "Eduard Staniloiu"
5 | ],
6 | "targetType": "library",
7 | "description": "A new collections framework for the D Standard Library",
8 | "copyright": "The D Language Foundation",
9 | "license": "BSL-1.0",
10 | "-ddoxFilterArgs": [
11 | "--unittest-examples",
12 | "--min-protection=Protected"
13 | ],
14 | "-ddoxTool": "scod",
15 | "dependencies": {
16 | "Phobos:allocator": "~master"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | collections
2 | -----------
3 |
4 | [](http://code.dlang.org/packages/collections)
5 | [](https://travis-ci.org/dlang-stdx/collections)
6 | [](https://codecov.io/gh/dlang-stdx/collections)
7 | [](https://github.com/dlang-stdx/collections/blob/master/LICENSE_1_0.txt)
8 |
9 | Documentation: https://dlang-stdx.github.io/collections/
10 |
--------------------------------------------------------------------------------
/doc/custom.css:
--------------------------------------------------------------------------------
1 | span#search-query {
2 | width: 12em;
3 | }
4 |
5 | /*
6 | custom colors
7 | */
8 |
9 | #top {
10 | background: #848382;
11 | border-bottom: 1px #000 solid;
12 | }
13 |
14 | #top #cssmenu > ul > li > ul
15 | {
16 | background: #848382;
17 | border: 1px solid #181829;
18 | }
19 |
20 | #top a:hover, #top #cssmenu li.open > a, #top #cssmenu li.active > a {
21 | background: #181829;
22 | }
23 |
24 | a, .question, .expand-toggle {
25 | color: #116;
26 | }
27 |
28 | a:hover, .question:hover, .expand-toggle:hover {
29 | color: #181829;
30 | }
31 |
32 | .d_decl {
33 | border-left: 5px solid #181829;
34 | }
35 |
36 | h1, h2, h3, h4, h5, h6
37 | {
38 | font-weight: normal;
39 | line-height: normal;
40 | text-align: left;
41 | margin: 0.2em 0 0.1em;
42 | }
43 |
--------------------------------------------------------------------------------
/.codecov.yml:
--------------------------------------------------------------------------------
1 | # Documentation: https://docs.codecov.io/docs/codecov-yaml
2 |
3 | codecov:
4 | # At CircleCi, the PR is merged into `master` before the testsuite is run.
5 | # This allows CodeCov to adjust the resulting coverage diff, s.t. it matches
6 | # with the GitHub diff.
7 | # https://github.com/codecov/support/issues/363
8 | # https://docs.codecov.io/v4.3.6/docs/comparing-commits
9 | allow_coverage_offsets: true
10 |
11 | coverage:
12 | precision: 3
13 | round: down
14 | range: "80...100"
15 |
16 | # Learn more at https://docs.codecov.io/docs/commit-status
17 | status:
18 | project:
19 | default:
20 | informational: true
21 | # Informational doesn't work with project yet
22 | threshold: 0.1
23 | patch:
24 | default:
25 | informational: true
26 | changes:
27 | default:
28 | informational: true
29 |
30 | comment: false
31 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | os:
3 | - linux
4 | language: d
5 | d:
6 | - ldc
7 | matrix:
8 | include:
9 | - d: dmd-nightly
10 | script:
11 | - dub test --compiler=$DC --build=unittest-cov
12 | #- dub build -b ddox
13 | - make -f doc/Makefile html
14 | addons:
15 | apt:
16 | packages:
17 | - libevent-dev
18 | after_success:
19 | - bash <(curl -s https://codecov.io/bash)
20 | deploy:
21 | local_dir: web
22 | provider: pages
23 | skip_cleanup: true
24 | github_token: $GH_REPO_TOKEN
25 | on:
26 | branch: master
27 | env:
28 | global:
29 | secure: BXASX8LWjLhUpKEkFACTUj9PhJ4aAf+K+SZAWL93DMevfRlQavAB4s7NEBZHl4uZeYw35MmnyHq8gxehLCdAsJyMIgw4tpg9c6wSV49K1kpIzwcahUAvaTMCMkzwJ8pTMF3RJo2T/VH5hPQwnCammbbzabG5UAT7i7smFi5+FjZ3wmYzKe/Enjiq9+DCOIhQmNBEnVrF8J2wuJAE6Sg7mrJEThP9CFS1nMJcZ3B1HNfWKK8gDW95K5qnGksAt+LFfFE6T/Rfd0Br3URVG7h4WInMtlWu4qMLjpJq0fvSM3dQazXddLjlgMEppdwxg7x4cmDjd1EHPY3MefWUOK9l2n2+E2LLIsaZ6vMQVmOFexgauniap8cUVWpDoPaYfOeWjhljWwC3EVmynl6UbfQJGE8cpA6SsSnJhlqhxwjVfcKz6BQ24/e9Krs99Hr898+P1h99JbqgwpaLPbacSqKyKlUXPNYMOd56bpiUfiP501rqjj9PPZx2KHFEuQ5OMbpx/uPwp7apP5cmnRQo0QzT17hsXdjJ7wRLK8+a4y+ZIRb6k+/E0tqoCIMFfndrnf5EhnU56VR/ghqThg6fe/puslgAUQCBoQM1cAKslfM89LxXQp0QPlB8pYQrp28Kvf6H8I2wGku8O24lZ/5nIj0YL5zQ4gaayJeXDQWwli/uwug=
30 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Boost Software License - Version 1.0 - August 17th, 2003
2 |
3 | Permission is hereby granted, free of charge, to any person or organization
4 | obtaining a copy of the software and accompanying documentation covered by
5 | this license (the "Software") to use, reproduce, display, distribute,
6 | execute, and transmit the Software, and to prepare derivative works of the
7 | Software, and to permit third-parties to whom the Software is furnished to
8 | do so, all subject to the following:
9 |
10 | The copyright notices in the Software and this entire statement, including
11 | the above license grant, this restriction and the following disclaimer,
12 | must be included in all copies of the Software, in whole or in part, and
13 | all derivative works of the Software, unless such copies or derivative
14 | works are solely in the form of machine-executable object code generated by
15 | a source language processor.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 | DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/doc/run_examples_custom.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Run all unittest examples
3 | *
4 | * Copyright 2016 by D Language Foundation
5 | *
6 | * License: http://boost.org/LICENSE_1_0.txt, Boost License 1.0
7 | */
8 |
9 | // wraps a unittest into a runnable script
10 | function wrapIntoMain(code) {
11 | var currentPackage = $('body')[0].id;
12 | var codeOut = '/+dub.sdl:\ndependency "stdx.collections" version="~>'+currentVersion+'"\n+/\n';
13 |
14 | // dynamically wrap into main if needed
15 | if (code.indexOf("void main") >= 0) {
16 | codeOut += "import " + currentPackage + "; ";
17 | codeOut += code;
18 | }
19 | else {
20 | codeOut += "void main()\n{\n";
21 | codeOut += " import " + currentPackage + ";\n";
22 | if (currentPackage !== "stdx.collections") {
23 | codeOut += " import stdx.collections;\n"
24 | }
25 | // writing to the stdout is probably often used
26 | codeOut += " import std.stdio: write, writeln, writef, writefln;\n ";
27 | codeOut += code.split("\n").join("\n ");
28 | codeOut += "\n}";
29 | }
30 | return codeOut;
31 | }
32 |
33 | $(document).ready(function()
34 | {
35 | if ($('body')[0].id == "Home")
36 | return;
37 |
38 | // only for std at the moment
39 | if (!$('body').hasClass("std"))
40 | return;
41 |
42 | // first selector is for ddoc - second for ddox
43 | var codeBlocks = $('pre[class~=d_code]').add('pre[class~=code]');
44 | codeBlocks.each(function(index)
45 | {
46 | var currentExample = $(this);
47 | var orig = currentExample.html();
48 |
49 | // check whether it is from a ddoced unittest
50 | // 1) check is for ddoc, 2) for ddox
51 | // manual created tests most likely can't be run without modifications
52 | if (!($(this).parent().parent().prev().hasClass("dlang_runnable") ||
53 | $(this).prev().children(":last").hasClass("dlang_runnable")))
54 | return;
55 |
56 | currentExample.replaceWith(
57 | '
'
58 | + '
'
59 | + '
'+orig+'
'
60 | + '
'
61 | + '
'
62 | + ''
63 | + '
'
64 | + '
'
70 | + '
Application outputRunning...
'
71 | + '
'
72 | );
73 | });
74 |
75 | $('textarea[class=d_code]').each(function(index) {
76 | var parent = $(this).parent();
77 | var btnParent = parent.parent().children(".d_example_buttons");
78 | var outputDiv = parent.parent().children(".d_code_output");
79 | var editor = setupTextarea(this, {
80 | parent: btnParent,
81 | outputDiv: outputDiv,
82 | stdin: false,
83 | args: false,
84 | transformOutput: wrapIntoMain,
85 | defaultOutput: "All tests passed",
86 | keepCode: true,
87 | outputHeight: "auto",
88 | backend: "tour"
89 | });
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | ###########################################################
2 | # This file builds the documentation.
3 | ###########################################################
4 |
5 | # tags
6 | LATEST:=$(shell git describe --abbrev=0 --tags | tr -d v)
7 |
8 | # binaries
9 | DMD=dmd
10 | RDMD=rdmd
11 | #DDOC=$(DMD) -w -c -o- -version=StdDdoc -version=Have
12 | DDOC=$(DMD) -w -c -o- -version=Have
13 |
14 | # folders
15 | DOC_OUTPUT_DIR=web
16 | DOC_SOURCE_DIR=doc
17 | GENERATED=.generated
18 | SOURCE_DIR=source
19 | DLANGORG_DIR=$(DOC_SOURCE_DIR)/dlang.org
20 |
21 | ###########################################################
22 | # setup packages
23 | ##########################################################
24 |
25 | # Packages. Just mention the package name here. The contents of package
26 | # xy/zz is in variable PACKAGE_xy_zz. This allows automation in iterating
27 | # packages and their modules.
28 | M = stdx
29 | PACKAGES = $M $M/collections
30 | PACKAGE_stdx_collections = array common dlist hashtable package slist
31 |
32 | MOD_EXCLUDES=$(addprefix --ex=,)
33 |
34 | ###########################################################
35 | # Setup macros + generate dynamic info needed
36 | ###########################################################
37 |
38 | all: html
39 |
40 | DLANGORG_MACROS=$(addprefix $(DLANGORG_DIR)/, macros html dlang.org)
41 | STDDOC=$(addsuffix .ddoc, ${DLANGORG_MACROS} ${GENERATED}/${LATEST} $(DLANGORG_DIR)/std $(DOC_SOURCE_DIR)/custom ${GENERATED}/modlist) $(NODATETIME)
42 |
43 | ${GENERATED}/${LATEST}.ddoc :
44 | mkdir -p $(dir $@)
45 | echo "LATEST=${LATEST}" >$@
46 | echo "LATEST_STABLE=$(shell git tag | grep -vE "(alpha|beta)" | tail -n1 | tr -d v)" >> $@
47 |
48 | ${GENERATED}/modlist.ddoc: $(DOC_SOURCE_DIR)/dlang.org/modlist.d $(SOURCE_DIR)
49 | mkdir -p $(dir $@)
50 | $(RDMD) --compiler=$(DMD) $< --dump=$M $(SOURCE_DIR) $(MOD_EXCLUDES) >$@
51 |
52 | ###########################################################
53 | # Makefile bootstrapping
54 | # It's mostly about the conversion from abc.foo -> abc_foo
55 | ###########################################################
56 |
57 | # Given one or more packages, returns the modules they contain
58 | P2MODULES=$(foreach P,$1,$(addprefix $P/,$(PACKAGE_$(subst /,_,$P))))
59 | P3_MODULES=$(call P2MODULES,$(PACKAGES))
60 | SRC_DOCUMENTABLES = index.d $(addsuffix .d,$(P3_MODULES))
61 |
62 | # D file to html, e.g. std/file -> std_file.html
63 | # But "package.d" is special cased: std/range/package.d -> std_range.html
64 | D2HTML=$(foreach p,$1,$(if $(subst package.d,,$(notdir $p)),$(subst /,_,$(subst .d,.html,$p)),$(subst /,_,$(subst /package.d,.html,$p))))
65 |
66 | HTMLS=$(addprefix $(DOC_OUTPUT_DIR)/, \
67 | $(call D2HTML, $(SRC_DOCUMENTABLES)))
68 |
69 | $(DOC_OUTPUT_DIR)/. :
70 | mkdir -p $@
71 |
72 | # everything except index.d needs a source path
73 | ADDSOURCE=$(if $(subst index.d,,$1),$(SOURCE_DIR)/$1,$1)
74 |
75 | # For each module, define a rule e.g.:
76 | # ../web/phobos/std_conv.html : std/conv.d $(STDDOC) ; ...
77 | $(foreach p,$(SRC_DOCUMENTABLES),$(eval \
78 | $(DOC_OUTPUT_DIR)/$(call D2HTML,$p) : $(call ADDSOURCE,$p) $(STDDOC) ;\
79 | $(DDOC) $(STDDOC) -I$(SOURCE_DIR) -Df$$@ $$<))
80 |
81 | ###########################################################
82 | # Setup all other resources needed by dlang.org
83 | ###########################################################
84 |
85 | IMAGES=favicon.ico
86 |
87 | JAVASCRIPT=$(addsuffix .js, $(addprefix js/, \
88 | codemirror-compressed dlang ddox listanchors run run_examples jquery-1.7.2.min))
89 |
90 | STYLES=$(addsuffix .css, $(addprefix css/, \
91 | style print custom codemirror))
92 |
93 | ALL_FILES = $(addprefix $(DOC_OUTPUT_DIR)/, \
94 | $(STYLES) $(IMAGES) $(JAVASCRIPT))
95 |
96 | $(DOC_OUTPUT_DIR)/css/custom.css: $(DOC_SOURCE_DIR)/custom.css
97 | @mkdir -p $(dir $@)
98 | cp $< $@
99 |
100 | $(DOC_OUTPUT_DIR)/js/run_examples.js: $(DOC_SOURCE_DIR)/run_examples_custom.js
101 | @mkdir -p $(dir $@)
102 | cp $< $@
103 |
104 | $(DOC_OUTPUT_DIR)/% : $(DLANGORG_DIR)/%
105 | @mkdir -p $(dir $@)
106 | cp $< $@
107 |
108 | html : $(DOC_OUTPUT_DIR)/. $(HTMLS) $(ALL_FILES)
109 |
110 | clean:
111 | rm -rf $(DOC_OUTPUT_DIR)
112 | rm -rf $(GENERATED)
113 |
114 | # prints the listed modules and sources
115 | debug:
116 | @echo $(SRC_DOCUMENTABLES)
117 | @echo $(STDDOC)
118 |
--------------------------------------------------------------------------------
/doc/custom.ddoc:
--------------------------------------------------------------------------------
1 | _=
2 | META_KEYWORDS=algorithm numeric containers collections
3 | META_DESCRIPTION=Collections library
4 | GITHUB_REPO=dlang-stdx/collections
5 | ROOT_DIR =
6 | SUBNAV=
7 | $(SUBNAV_TEMPLATE
8 | $(DIVC head,
9 | $(H2 Library Reference)
10 | $(P $(LINK2 index.html, overview))
11 | )
12 | $(UL $(MODULE_MENU))
13 | )
14 | _=
15 |
16 | PROJECT=collections
17 | PAGE_TOOLS=
18 | $(DIVID tools, $(DIV,
19 | $(DIVC tip smallprint,
20 | $(HTMLTAG3 a, href="https://github.com/$(GITHUB_REPO)/issues", Report a bug)
21 | $(DIV,
22 | If you spot a problem with this page, click here to create a Github issue.
23 | )
24 | )
25 | $(DIVC tip smallprint,
26 |
Improve this page
27 | $(DIV,
28 | Quickly fork, edit online, and submit a pull request for this page.
29 | Requires a signed-in GitHub account. This works well for small changes.
30 | If you'd like to make larger changes you may want to consider using
31 | a local clone.
32 | )
33 | )
34 | ))
35 | _=
36 |
37 | DDOC=
38 |
39 |
40 |
41 |
42 |
43 |
44 | $(T title, $(FULL_TITLE))
45 | $(COMMON_HEADERS_DLANG)
46 |
47 |
48 |
49 |
50 |
51 |
52 | $(EXTRA_HEADERS)
53 |
54 |
55 | $(SCRIPT document.body.className += ' have-javascript';
56 | var currentVersion = "$(LATEST_STABLE)";
57 | )
58 | $(DIVID top, $(DIVC helper, $(DIVC helper expand-container,
59 |
Menu
60 | $(NAVIGATION)
61 | $(DIVC search-container expand-container,
62 |
Search
63 | $(SEARCH_BOX)
64 | )
65 | )))
66 | $(LAYOUT_PREFIX)
67 | $(DIVC container,
68 | $(SUBNAV)
69 | $(DIVCID $(HYPHENATE), content,
70 | $(PAGE_TOOLS)
71 | $(LAYOUT_TITLE)
72 | $(BODY_PREFIX)
73 | $(BODY)
74 | $(FOOTER)
75 | )
76 | )
77 | $(COMMON_SCRIPTS)
78 | $(LAYOUT_SUFFIX)
79 |
80 |
81 |
82 | _=
83 | SEARCH_BOX=
84 | $(DIVID search-box,
85 |
92 | )
93 | _=
94 |
95 | NAVIGATION=
96 | $(DIVID cssmenu, $(UL
97 | $(MENU $(GITHUB_REPO), Github)
98 | $(MENU $(GITHUB_REPO)/issues, Issues)
99 | $(MENU https://dlang.org, DLang)
100 | ))
101 | _=
102 |
103 | _=
104 |
105 | COMMON_HEADERS_DLANG=
106 |
107 | _=
108 | COMMON_SCRIPTS =
109 | $(SCRIPTLOAD https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js)
110 | $(SCRIPT window.jQuery || document.write('\x3Cscript src="$(STATIC js/jquery-1.7.2.min.js)">\x3C/script>');$(EXTRA_JS))
111 | $(SCRIPTLOAD $(STATIC js/dlang.js))
112 | $(COMMON_SCRIPTS_DLANG)
113 | _=
114 | COMMON_SCRIPTS_DLANG =
115 | $(SCRIPTLOAD $(STATIC js/codemirror-compressed.js))
116 | $(SCRIPTLOAD $(STATIC js/run.js))
117 | $(SCRIPTLOAD $(STATIC js/run_examples.js))
118 | _=
119 |
120 | COMMON_HEADERS_DLANG=
121 | _=
122 |
123 | LAYOUT_SUFFIX =
124 | $(SCRIPTLOAD js/listanchors.js)
125 | $(SCRIPT jQuery(document).ready(listanchors);)
126 | _=
127 |
--------------------------------------------------------------------------------
/source/stdx/collections/package.d:
--------------------------------------------------------------------------------
1 | /**
2 | This module defines generic collections.
3 |
4 | Collection_primitives:
5 |
6 | Collections do not form a class hierarchy, instead they implement a
7 | common set of primitives (see table below). These primitives each guarantee
8 | a specific worst case complexity and thus allow generic code to be written
9 | independently of the _collection implementation.
10 |
11 | The following table describes the common set of primitives that collections
12 | implement. A _collection need not implement all primitives, but if a primitive
13 | is implemented, it must support the syntax described in the `syntax` column with
14 | the semantics described in the `description` column, and it must not have a
15 | worst-case complexity worse than denoted in big-O notation in the
16 | $(BIGOH ·) column. Below, `C` means a _collection type, `c` is a value of
17 | _collection type, $(D n$(SUBSCRIPT x)) represents the effective length of value
18 | `x`, which could be a single element (in which case $(D n$(SUBSCRIPT x)) is `1`),
19 | a _collection, or a range.
20 |
21 | $(BOOKTABLE collection primitives,
22 | $(TR
23 | $(TH Syntax)
24 | $(TH $(BIGOH ·))
25 | $(TH Description)
26 | )
27 | $(TR
28 | $(TDNW $(D C(x)))
29 | $(TDNW $(D n$(SUBSCRIPT x)))
30 | $(TD Creates a _collection of type $(D C) from either another _collection,
31 | a range or an element. The created _collection must not be a null
32 | reference even if x is empty.)
33 | )
34 | $(TR
35 | $(TDNW $(D c.dup))
36 | $(TDNW $(D n$(SUBSCRIPT c)))
37 | $(TD Returns a duplicate of the _collection.)
38 | )
39 | $(TR
40 | $(TDNW $(D c ~ x))
41 | $(TDNW $(D n$(SUBSCRIPT c) + n$(SUBSCRIPT x)))
42 | $(TD Returns the concatenation of `c` and `x`. `x` may be a single element
43 | or an input range.)
44 | )
45 | $(TR
46 | $(TDNW $(D x ~ c))
47 | $(TDNW $(D n$(SUBSCRIPT c) + n$(SUBSCRIPT x)))
48 | $(TD Returns the concatenation of `x` and `c`. `x` may be a single element
49 | or an input range type.)
50 | )
51 | $(LEADINGROWN 3, Iteration
52 | )
53 | $(TR
54 | $(TD $(D c.popFront()))
55 | $(TD `1`)
56 | $(TD Advances to the next element in the _collection.)
57 | )
58 | $(TR
59 | $(TD $(D c.save))
60 | $(TD `1`)
61 | $(TD Return a shallow copy of the _collection.)
62 | )
63 | $(TR
64 | $(TD $(D c[]))
65 | $(TDNW $(D n$(SUBSCRIPT c)))
66 | $(TD Returns a range
67 | iterating over the entire _collection, in a _collection-defined order.)
68 | )
69 | $(TR
70 | $(TDNW $(D c[a .. b]))
71 | $(TDNW $(D n$(SUBSCRIPT c)))
72 | $(TD Fetches a portion of the _collection from key `a` to key `b`.)
73 | )
74 | $(LEADINGROWN 3, Capacity
75 | )
76 | $(TR
77 | $(TD $(D c.empty))
78 | $(TD `1`)
79 | $(TD Returns `true` if the _collection has no elements, `false` otherwise.)
80 | )
81 | $(TR
82 | $(TD $(D c.length))
83 | $(TDNW `1`)
84 | $(TD Returns the number of elements in the _collection.)
85 | )
86 | $(TR
87 | $(TDNW $(D c.length = n))
88 | $(TDNW $(D max(n$(SUBSCRIPT c), n)))
89 | $(TD Forces the number of elements in the _collection to `n`. If the
90 | _collection ends up growing, the added elements are initialized
91 | in a _collection-dependent manner (usually with $(D T.init)).)
92 | )
93 | $(TR
94 | $(TD $(D c.capacity))
95 | $(TDNW $(D n$(SUBSCRIPT c)))
96 | $(TD Returns the maximum number of elements that can be stored in the
97 | _collection without triggering a reallocation.)
98 | )
99 | $(TR
100 | $(TD $(D c.reserve(x)))
101 | $(TD $(D n$(SUBSCRIPT c)))
102 | $(TD Forces `capacity` to at least `x` without reducing it.)
103 | )
104 | $(LEADINGROWN 3, Access
105 | )
106 | $(TR
107 | $(TDNW $(D c.front))
108 | $(TDNW `1`)
109 | $(TD Returns the first element of the _collection, in a _collection-defined
110 | order.)
111 | )
112 | $(TR
113 | $(TDNW $(D c.front = v))
114 | $(TDNW `1`)
115 | $(TD Assigns `v` to the first element of the _collection.)
116 | )
117 | $(TR
118 | $(TDNW $(D c.back))
119 | $(TDNW $(D log n$(SUBSCRIPT c)))
120 | $(TD Returns the last element of the _collection, in a _collection-defined order.)
121 | )
122 | $(TR
123 | $(TDNW $(D c.back = v))
124 | $(TDNW $(D n$(SUBSCRIPT c)))
125 | $(TD Assigns `v` to the last element of the _collection.)
126 | )
127 | $(TR
128 | $(TDNW $(D c[x]))
129 | $(TDNW $(D n$(SUBSCRIPT c)))
130 | $(TD Provides indexed access into the _collection. The index type is
131 | _collection-defined. A _collection may define several index types (and
132 | consequently overloaded indexing).)
133 | )
134 | $(TR
135 | $(TDNW $(D c[x] = v))
136 | $(TDNW $(D n$(SUBSCRIPT c)))
137 | $(TD Sets element at specified index into the _collection.)
138 | )
139 | $(TR
140 | $(TDNW $(D c[x] $(I op)= v))
141 | $(TDNW $(D n$(SUBSCRIPT c)))
142 | $(TD Performs read-modify-write operation at specified index into the
143 | _collection.)
144 | )
145 | $(LEADINGROWN 3, Operations
146 | )
147 | $(TR
148 | $(TDNW $(D e in c))
149 | $(TDNW $(D n$(SUBSCRIPT c)))
150 | $(TD Returns nonzero if e is found in $(D c).)
151 | )
152 | $(LEADINGROWN 3, Modifiers
153 | )
154 | $(TR
155 | $(TDNW $(D c ~= x))
156 | $(TDNW $(D n$(SUBSCRIPT c) + n$(SUBSCRIPT x)))
157 | $(TD Appends `x` to `c`. `x` may be a single element or an input range type.)
158 | )
159 | $(TR
160 | $(TDNW $(D c.clear()))
161 | $(TDNW $(D n$(SUBSCRIPT c)))
162 | $(TD Removes all elements in `c`.)
163 | )
164 | $(TR
165 | $(TDNW $(D c.insert(pos, x)))
166 | $(TDNW $(D n$(SUBSCRIPT x)))
167 | $(TD Inserts `x` at `pos` in `c`. `x` may be a single element or an input
168 | range type.)
169 | )
170 | $(TR
171 | $(TDNW $(D c.insertBack(x)))
172 | $(TDNW $(D n$(SUBSCRIPT c) + n$(SUBSCRIPT x)))
173 | $(TD Inserts `x` at the back of `c`. `x` may be a single element or an input
174 | range type.)
175 | )
176 | $(TR
177 | $(TDNW $(D c.remove()))
178 | $(TDNW $(D 1))
179 | $(TD Removes the front element of `c`.)
180 | )
181 |
182 | $(TR
183 | $(TDNW $(D ))
184 | $(TDNW $(D ))
185 | $(TD )
186 | )
187 | )
188 |
189 | Source: $(PHOBOSSRC std/experimental/_collection/package.d)
190 |
191 | License: Distributed under the Boost Software License, Version 1.0.
192 | (See accompanying file LICENSE_1_0.txt or copy at $(HTTP
193 | boost.org/LICENSE_1_0.txt)).
194 |
195 | Authors: Eduard Staniloiu, $(HTTP erdani.com, Andrei Alexandrescu)
196 | */
197 |
198 | module stdx.collections;
199 |
--------------------------------------------------------------------------------
/source/stdx/collections/common.d:
--------------------------------------------------------------------------------
1 | /**
2 | Utility and ancillary artifacts of `stdx.collections`.
3 | */
4 | module stdx.collections.common;
5 | import std.range: isInputRange;
6 |
7 | auto tail(Collection)(Collection collection)
8 | if (isInputRange!Collection)
9 | {
10 | collection.popFront();
11 | return collection;
12 | }
13 |
14 | package auto threadAllocatorObject()
15 | {
16 | import std.experimental.allocator : RCIAllocator;
17 |
18 | static @nogc nothrow
19 | RCIAllocator wrapAllocatorObject()
20 | {
21 | import std.experimental.allocator.gc_allocator : GCAllocator;
22 | import std.experimental.allocator : allocatorObject;
23 |
24 | return allocatorObject(GCAllocator.instance);
25 | }
26 | auto fn = (() @trusted =>
27 | cast(RCIAllocator function() @nogc nothrow pure @safe)(&wrapAllocatorObject))();
28 | return fn();
29 | }
30 |
31 | package auto processAllocatorObject()
32 | {
33 | import std.experimental.allocator : RCISharedAllocator;
34 |
35 | static @nogc nothrow
36 | RCISharedAllocator wrapAllocatorObject()
37 | {
38 | import std.experimental.allocator.gc_allocator : GCAllocator;
39 | import std.experimental.allocator : sharedAllocatorObject;
40 |
41 | return sharedAllocatorObject(GCAllocator.instance);
42 | }
43 | auto fn = (() @trusted =>
44 | cast(RCISharedAllocator function() @nogc nothrow pure @safe)(&wrapAllocatorObject))();
45 | return fn();
46 | }
47 |
48 | // Returns an instance of the default allocator
49 | package auto defaultAllocator(Q)()
50 | {
51 | static if (is(Q == immutable) || is(Q == const))
52 | return processAllocatorObject();
53 | else
54 | return threadAllocatorObject();
55 | }
56 |
57 | package struct AllocatorHandler
58 | {
59 | import std.experimental.allocator : RCIAllocator, RCISharedAllocator,
60 | dispose, stateSize, theAllocator, processAllocator;
61 | import std.experimental.allocator.building_blocks.affix_allocator;
62 | import std.conv : emplace;
63 | import core.atomic : atomicOp;
64 | import std.algorithm.mutation : move;
65 | debug(AllocatorHandler) import std.stdio;
66 |
67 | private union
68 | {
69 | void *_;
70 | size_t _pMeta;
71 | }
72 |
73 | alias LocalAllocT = AffixAllocator!(RCIAllocator, size_t);
74 | alias SharedAllocT = shared AffixAllocator!(RCISharedAllocator, size_t);
75 |
76 | private static struct Metadata
77 | {
78 | union LAllocator
79 | {
80 | LocalAllocT alloc;
81 | }
82 | union SAllocator
83 | {
84 | SharedAllocT alloc;
85 | }
86 |
87 | LAllocator _localAlloc;
88 | SAllocator _sharedAlloc;
89 | bool _isShared;
90 | size_t _rc = 1;
91 | }
92 |
93 | pragma(inline, true)
94 | pure nothrow @trusted @nogc
95 | bool isNull() const
96 | {
97 | return (cast(void*) _pMeta) is null;
98 | }
99 |
100 | pragma(inline, true)
101 | pure nothrow @safe @nogc
102 | bool isShared() const
103 | {
104 | return isSharedMeta(_pMeta);
105 | }
106 |
107 | nothrow pure @trusted
108 | this(A, this Q)(A alloc)
109 | if (!is(Q == shared)
110 | && (is(A == RCISharedAllocator) || !is(Q == immutable))
111 | && (is(A == RCIAllocator) || is(A == RCISharedAllocator)))
112 | {
113 | //assert(alloc.alignment >= Metadata.alignof);
114 |
115 | // Allocate mem for metadata
116 | //auto state = alloc.allocate(stateSize!Metadata);
117 |
118 | auto dg = cast(void[] delegate(size_t, TypeInfo) nothrow pure)(&alloc.allocate);
119 | auto state = dg(stateSize!Metadata, null);
120 | assert(state !is null);
121 |
122 | auto meta = emplace!Metadata(state);
123 | assert(state.ptr == meta);
124 | assert(meta._rc == 1);
125 |
126 | static if (is(A == RCISharedAllocator))
127 | {
128 | auto shAlloc = SharedAllocT(alloc);
129 | auto sz = stateSize!SharedAllocT;
130 | (cast(void*) &meta._sharedAlloc.alloc)[0 .. sz] = (cast(void*) &shAlloc)[0 .. sz];
131 | meta._isShared = true;
132 | SharedAllocT init;
133 | (cast(void*) &shAlloc)[0 .. sz] = (cast(void*) &init)[0 .. sz];
134 | }
135 | else
136 | {
137 | auto lcAlloc = LocalAllocT(alloc);
138 | move(lcAlloc, meta._localAlloc.alloc);
139 | }
140 | _pMeta = cast(size_t) state.ptr;
141 | }
142 |
143 | pure nothrow @safe @nogc
144 | //this(this Q)(ref Q rhs)
145 | this(const ref typeof(this) rhs)
146 | {
147 | assert((() @trusted => (cast(void*) _pMeta) is null)());
148 | _pMeta = rhs._pMeta;
149 | incRef(_pMeta);
150 | }
151 |
152 | pure nothrow @safe /*@nogc*/
153 | ref typeof(this) opAssign(const ref typeof(this) rhs)
154 | {
155 | debug(AllocatorHandler)
156 | {
157 | writefln("AllocatorHandler.opAssign: begin");
158 | scope(exit) writefln("AllocatorHandler.opAssign: end");
159 | }
160 |
161 | auto pMeta = (() @trusted => cast(void*) _pMeta)();
162 | auto rhspMeta = (() @trusted => cast(void*) rhs._pMeta)();
163 | if (rhspMeta !is null && _pMeta == rhs._pMeta)
164 | {
165 | return this;
166 | }
167 | if (rhspMeta !is null)
168 | {
169 | rhs.incRef(rhs._pMeta);
170 | debug(AllocatorHandler) writefln(
171 | "AllocatorHandler.opAssign: AllocatorHandler %s has refcount: %s",
172 | &this, rhs.getRC);
173 | }
174 | if (pMeta) decRef(_pMeta);
175 | _pMeta = rhs._pMeta;
176 | return this;
177 | }
178 |
179 | pure nothrow @safe @nogc
180 | void bootstrap(this Q)()
181 | {
182 | assert((() @trusted => cast(void*) _pMeta)());
183 | incRef(_pMeta);
184 | }
185 |
186 | pure nothrow @safe /*@nogc*/
187 | ~this()
188 | {
189 | auto pMeta = (() @trusted => cast(void*) _pMeta)();
190 | if (pMeta is null)
191 | {
192 | debug(AllocatorHandler) writeln("META IS NULL");
193 | return;
194 | }
195 | decRef(_pMeta);
196 | }
197 |
198 | //debug(AllocatorHandler)
199 | pragma(inline, true)
200 | private pure nothrow @trusted @nogc
201 | size_t getRC(this _)()
202 | {
203 | auto meta = cast(Metadata*) _pMeta;
204 | return meta._rc;
205 | }
206 |
207 | pragma(inline, true)
208 | static private pure nothrow @trusted @nogc
209 | bool isSharedMeta(const size_t pMeta)
210 | {
211 | assert(cast(void*) pMeta);
212 | auto meta = cast(Metadata*) pMeta;
213 | return meta._isShared;
214 | }
215 |
216 | pragma(inline, true)
217 | static private pure nothrow @trusted @nogc
218 | ref auto localAllocator(const size_t pMeta)
219 | {
220 | assert(cast(void*) pMeta);
221 | auto meta = cast(Metadata*) pMeta;
222 | assert(!meta._isShared);
223 | return meta._localAlloc.alloc;
224 | }
225 |
226 | pragma(inline, true)
227 | static private pure nothrow @trusted @nogc
228 | ref auto sharedAllocator(const size_t pMeta)
229 | {
230 | assert(cast(void*) pMeta);
231 | auto meta = cast(Metadata*) pMeta;
232 | assert(meta._isShared);
233 | return meta._sharedAlloc.alloc;
234 | }
235 |
236 | static private @nogc nothrow pure @trusted
237 | void incRef(const size_t pMeta)
238 | {
239 | auto tmeta = cast(Metadata*) pMeta;
240 | if (tmeta._isShared)
241 | {
242 | auto meta = cast(shared Metadata*) pMeta;
243 | atomicOp!"+="(meta._rc, 1);
244 | }
245 | else
246 | {
247 | auto meta = cast(Metadata*) pMeta;
248 | ++meta._rc;
249 | }
250 | }
251 |
252 | static private @nogc nothrow pure @trusted
253 | void decRef(const size_t pMeta)
254 | {
255 | auto tmeta = cast(Metadata*) pMeta;
256 | void[] origState = (cast(void*) tmeta)[0 .. stateSize!Metadata];
257 |
258 | if (tmeta._isShared)
259 | {
260 | auto meta = cast(shared Metadata*) pMeta;
261 | debug(AllocatorHandler) writeln("is shared");
262 | if (atomicOp!"-="(meta._rc, 1) == 0)
263 | {
264 | debug(AllocatorHandler) writeln("Here 2");
265 | SharedAllocT a;
266 | // Bitblast the allocator on the stack copy; this will ensure that the
267 | // dtor inside the union will be called
268 | // Workaround for move
269 | auto sz = stateSize!SharedAllocT;
270 | (cast(void*) &a)[0 .. sz] = (cast(void*) &meta._sharedAlloc.alloc)[0 .. sz];
271 | SharedAllocT init;
272 | (cast(void*) &meta._sharedAlloc.alloc)[0 .. sz] = (cast(void*) &init)[0 .. sz];
273 | //a.parent.deallocate(origState);
274 | (cast(bool delegate(void[]) @nogc nothrow pure)(&a.parent.deallocate))(origState);
275 | }
276 | }
277 | else
278 | {
279 | debug(AllocatorHandler) writeln("is not shared");
280 | auto meta = cast(Metadata*) pMeta;
281 | if (--meta._rc == 0)
282 | {
283 | debug(AllocatorHandler) writeln("Here 3");
284 | LocalAllocT a;
285 | move(meta._localAlloc.alloc, a);
286 | //assert(meta._localAlloc.alloc == LocalAllocT.init);
287 | //a.parent.deallocate(origState);
288 | (cast(bool delegate(void[]) @nogc nothrow pure)(&a.parent.deallocate))(origState);
289 | }
290 | }
291 | }
292 |
293 | nothrow:
294 |
295 | pure @trusted
296 | void[] allocate(size_t n) const
297 | {
298 | return (cast(void[] delegate(size_t) const nothrow pure)(&_allocate))(n);
299 | }
300 |
301 | void[] _allocate(size_t n) const
302 | {
303 | assert(cast(void*) _pMeta);
304 | return isSharedMeta(_pMeta) ?
305 | sharedAllocator(_pMeta).allocate(n) :
306 | localAllocator(_pMeta).allocate(n);
307 | }
308 |
309 | pure @trusted
310 | bool expand(ref void[] b, size_t delta) const
311 | {
312 | return (cast(bool delegate(ref void[], size_t) const nothrow pure)(&_expand))(b, delta);
313 | }
314 |
315 | bool _expand(ref void[] b, size_t delta) const
316 | {
317 | assert(cast(void*) _pMeta);
318 | return isSharedMeta(_pMeta) ?
319 | sharedAllocator(_pMeta).expand(b, delta) :
320 | localAllocator(_pMeta).expand(b, delta);
321 | }
322 |
323 | pure
324 | bool deallocate(void[] b) const
325 | {
326 | return (cast(bool delegate(void[]) const nothrow pure)(&_deallocate))(b);
327 | }
328 |
329 | bool _deallocate(void[] b) const
330 | {
331 | assert(cast(void*) _pMeta);
332 | return isSharedMeta(_pMeta) ?
333 | sharedAllocator(_pMeta).deallocate(b) :
334 | localAllocator(_pMeta).deallocate(b);
335 | }
336 |
337 | @nogc nothrow pure @trusted
338 | private size_t prefix(T)(const T[] b) const
339 | {
340 | assert(cast(void*) _pMeta);
341 | return isSharedMeta(_pMeta) ?
342 | cast(size_t)&sharedAllocator(_pMeta).prefix(b) :
343 | cast(size_t)&localAllocator(_pMeta).prefix(b);
344 | }
345 |
346 | @nogc nothrow pure @trusted
347 | size_t opPrefix(string op, T)(const T[] support, size_t val) const
348 | if ((op == "+=") || (op == "-="))
349 | {
350 | assert(cast(void*) _pMeta);
351 | if (isSharedMeta(_pMeta))
352 | {
353 | return cast(size_t)(atomicOp!op(*cast(shared size_t *)prefix(support), val));
354 | }
355 | else
356 | {
357 | mixin("return cast(size_t)(*cast(size_t *)prefix(support)" ~ op ~ "val);");
358 | }
359 | }
360 |
361 | @nogc nothrow pure @trusted
362 | size_t opCmpPrefix(string op, T)(const T[] support, size_t val) const
363 | if ((op == "==") || (op == "<=") || (op == "<") || (op == ">=") || (op == ">"))
364 | {
365 | assert(cast(void*) _pMeta);
366 | if (isSharedMeta(_pMeta))
367 | {
368 | return cast(size_t)(atomicOp!op(*cast(shared size_t *)prefix(support), val));
369 | }
370 | else
371 | {
372 | mixin("return cast(size_t)(*cast(size_t *)prefix(support)" ~ op ~ "val);");
373 | }
374 | }
375 |
376 | /*@nogc*/ nothrow pure @safe
377 | AllocatorHandler getSharedAlloc() const
378 | {
379 | if (isNull || !isShared)
380 | {
381 | return AllocatorHandler(processAllocatorObject());
382 | }
383 | return AllocatorHandler(this);
384 | }
385 |
386 | @nogc nothrow pure @safe
387 | RCIAllocator getLocalAlloc() const
388 | {
389 | if (isNull || isShared)
390 | {
391 | return threadAllocatorObject();
392 | }
393 | return localAllocator(_pMeta).parent;
394 | }
395 | }
396 |
397 | version(unittest)
398 | {
399 | // Structs used to test the type system inference
400 | package static struct Unsafe
401 | {
402 | int _x;
403 | @system this(int x) {}
404 | }
405 |
406 | package static struct UnsafeDtor
407 | {
408 | int _x;
409 | @nogc nothrow pure @safe this(int x) {}
410 | @system ~this() {}
411 | }
412 |
413 | package static struct Impure
414 | {
415 | import std.experimental.allocator : RCIAllocator, theAllocator;
416 | RCIAllocator _a;
417 | @safe this(int id) { _a = theAllocator; }
418 | }
419 |
420 | package static struct ImpureDtor
421 | {
422 | import std.experimental.allocator : RCIAllocator, theAllocator;
423 | RCIAllocator _a;
424 | @nogc nothrow pure @safe this(int x) {}
425 | @safe ~this() { _a = theAllocator; }
426 | }
427 |
428 | package static struct Throws
429 | {
430 | import std.exception : enforce;
431 | int _x;
432 | this(int id) { enforce(id > 0); }
433 | }
434 |
435 | package static struct ThrowsDtor
436 | {
437 | import std.exception : enforce;
438 | int _x;
439 | @nogc nothrow pure @safe this(int x) {}
440 | ~this() { enforce(_x > 0); }
441 | }
442 | }
443 |
444 | unittest
445 | {
446 | import std.experimental.allocator.mallocator;
447 | import std.experimental.allocator.building_blocks.stats_collector;
448 | import std.experimental.allocator : RCIAllocator, RCISharedAllocator,
449 | allocatorObject, sharedAllocatorObject, processAllocator, theAllocator;
450 | import std.conv : to;
451 | import std.stdio;
452 | import std.traits;
453 |
454 | struct MyA(A)
455 | {
456 | A a;
457 | alias a this;
458 |
459 | pure nothrow @nogc
460 | bool deallocate(void[] b)
461 | {
462 | return (cast(bool delegate(void[]) pure nothrow @nogc)(&a.deallocate))(b);
463 | }
464 |
465 | bool forceAttDealloc(void[] b)
466 | {
467 | return a.deallocate(b);
468 | }
469 | }
470 |
471 | //alias SCAlloc = MyA!(StatsCollector!(Mallocator, Options.bytesUsed));
472 | alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed);
473 | SCAlloc statsCollectorAlloc;
474 | size_t bytesUsed = statsCollectorAlloc.bytesUsed;
475 | assert(bytesUsed == 0);
476 | {
477 | auto _allocator = allocatorObject(&statsCollectorAlloc);
478 | auto sca = AllocatorHandler(_allocator);
479 | auto buf = sca.allocate(10);
480 | assert(buf.length == 10);
481 |
482 | auto t = cast(size_t*)(sca.prefix(buf));
483 | assert(*t == 0);
484 | *t += 1;
485 | assert(*t == *cast(size_t*)sca.prefix(buf));
486 | sca.deallocate(buf);
487 | }
488 | bytesUsed = statsCollectorAlloc.bytesUsed;
489 | assert(bytesUsed == 0, "MutableDualAlloc ref count leaks memory; leaked "
490 | ~ to!string(bytesUsed) ~ " bytes");
491 |
492 | // Test immutable allocator
493 | auto ia = immutable AllocatorHandler(processAllocator);
494 | auto buf = ia.allocate(10);
495 | assert(buf.length == 10);
496 | ia.deallocate(buf);
497 |
498 | static assert(!__traits(compiles, { auto ia2 = immutable AllocatorHandler(theAllocator); }));
499 | auto ca = const AllocatorHandler(theAllocator);
500 | }
501 |
502 | unittest
503 | {
504 | import std.experimental.allocator : RCIAllocator, RCISharedAllocator,
505 | allocatorObject, sharedAllocatorObject, processAllocator, theAllocator;
506 | import std.stdio;
507 |
508 | auto sca = immutable AllocatorHandler(processAllocator);
509 | auto buf = sca.allocate(10);
510 | assert(buf.length == 10);
511 | sca.deallocate(buf);
512 |
513 | auto al = sca.getSharedAlloc;
514 | AllocatorHandler al2 = al.getSharedAlloc;
515 | assert(al._pMeta == al2._pMeta);
516 | }
517 |
518 | enum allocatorHandler = q{
519 | AllocatorHandler _allocator;
520 |
521 | /*
522 | Constructs the ouroboros allocator from allocator if the ouroboros
523 | allocator wasn't previously set
524 | */
525 | /*@nogc*/ nothrow pure @safe
526 | bool setAllocator(A)(ref A allocator)
527 | if (is(A == RCIAllocator) || is(A == RCISharedAllocator))
528 | {
529 | if (_allocator.isNull)
530 | {
531 | auto a = typeof(_allocator)(allocator);
532 | move(a, _allocator);
533 | return true;
534 | }
535 | return false;
536 | }
537 | };
538 |
--------------------------------------------------------------------------------
/source/stdx/collections/rcstring.d:
--------------------------------------------------------------------------------
1 | /**
2 | `RCString` is a reference-counted string which is based on
3 | $(REF Array, std,experimental,collections) of `ubyte`s.
4 | By default, `RCString` is not a range. The `.by` helpers can be used to specify
5 | the iteration mode.
6 | RCString internally stores the string as UTF-8 $(REF Array, stdx,collections,array).
7 |
8 | $(UL
9 | $(LI `str.by!char` - iterates over individual `char` characters. No auto-decoding is done.)
10 | $(LI `str.by!wchar` - iterates over `wchar` characters. Auto-decoding is done.)
11 | $(LI `str.by!dchar` - iterates over `dchar` characters. Auto-decoding is done.)
12 | $(LI `str.by!ubyte`- iterates over the raw `ubyte` representation. No auto-decoding is done. This is similar to $(REF representation, std,string) for built-in strings)
13 | )
14 | */
15 | module stdx.collections.rcstring;
16 |
17 | import stdx.collections.common;
18 | import stdx.collections.array;
19 | import std.range.primitives : isInputRange, ElementType, hasLength;
20 | import std.traits : isSomeChar, isSomeString;
21 |
22 | debug(CollectionRCString) import std.stdio;
23 |
24 | version(unittest)
25 | {
26 | import std.experimental.allocator.mallocator;
27 | import std.experimental.allocator.building_blocks.stats_collector;
28 | import std.experimental.allocator : RCIAllocator, RCISharedAllocator,
29 | allocatorObject, sharedAllocatorObject;
30 | import std.algorithm.mutation : move;
31 | import std.stdio;
32 |
33 | private alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed);
34 | }
35 |
36 | ///
37 | struct RCString
38 | {
39 | private:
40 | Array!ubyte _support;
41 | mixin(allocatorHandler);
42 | public:
43 |
44 | /**
45 | Constructs a qualified rcstring that will use the provided
46 | allocator object. For `immutable` objects, a `RCISharedAllocator` must
47 | be supplied.
48 |
49 | Params:
50 | allocator = a $(REF RCIAllocator, std,experimental,allocator) or
51 | $(REF RCISharedAllocator, std,experimental,allocator)
52 | allocator object
53 |
54 | Complexity: $(BIGOH 1)
55 | */
56 | this(A, this Q)(A allocator)
57 | if (!is(Q == shared)
58 | && (is(A == RCISharedAllocator) || !is(Q == immutable))
59 | && (is(A == RCIAllocator) || is(A == RCISharedAllocator)))
60 | {
61 | debug(CollectionRCString)
62 | {
63 | writefln("RCString.ctor: begin");
64 | scope(exit) writefln("RCString.ctor: end");
65 | }
66 | static if (is(Q == immutable) || is(Q == const))
67 | _allocator = immutable AllocatorHandler(allocator);
68 | else
69 | setAllocator(allocator);
70 | }
71 |
72 | ///
73 | @safe unittest
74 | {
75 | import std.experimental.allocator : theAllocator, processAllocator;
76 |
77 | auto a = RCString(theAllocator);
78 | auto ca = const RCString(processAllocator);
79 | auto ia = immutable RCString(processAllocator);
80 | }
81 |
82 | /**
83 | Constructs a qualified rcstring out of a number of bytes
84 | that will use the provided allocator object.
85 | For `immutable` objects, a `RCISharedAllocator` must be supplied.
86 | If no allocator is passed, the default allocator will be used.
87 |
88 | Params:
89 | allocator = a $(REF RCIAllocator, std,experimental,allocator) or
90 | $(REF RCISharedAllocator, std,experimental,allocator)
91 | allocator object
92 | bytes = a variable number of bytes, either in the form of a
93 | list or as a built-in RCString
94 |
95 | Complexity: $(BIGOH m), where `m` is the number of bytes.
96 | */
97 | this()(ubyte[] bytes...)
98 | {
99 | this(defaultAllocator!(typeof(this)), bytes);
100 | }
101 |
102 | ///
103 | @safe unittest
104 | {
105 | // Create a list from a list of bytes
106 | auto a = RCString('1', '2', '3');
107 |
108 | // Create a list from an array of bytes
109 | auto b = RCString(['1', '2', '3']);
110 |
111 | // Create a const list from a list of bytes
112 | auto c = const RCString('1', '2', '3');
113 | }
114 |
115 | /// ditto
116 | this(A, this Q)(A allocator, ubyte[] bytes...)
117 | if (!is(Q == shared)
118 | && (is(A == RCISharedAllocator) || !is(Q == immutable))
119 | && (is(A == RCIAllocator) || is(A == RCISharedAllocator)))
120 | {
121 | this(allocator);
122 | _support = typeof(_support)(allocator, bytes);
123 | }
124 |
125 | ///
126 | @safe unittest
127 | {
128 | import std.experimental.allocator : theAllocator, processAllocator;
129 |
130 | // Create a list from a list of ints
131 | auto a = RCString(theAllocator, '1', '2', '3');
132 |
133 | // Create a list from an array of ints
134 | auto b = RCString(theAllocator, ['1', '2', '3']);
135 | }
136 |
137 | /**
138 | Constructs a qualified rcstring out of a string
139 | that will use the provided allocator object.
140 | For `immutable` objects, a `RCISharedAllocator` must be supplied.
141 | If no allocator is passed, the default allocator will be used.
142 |
143 | Params:
144 | allocator = a $(REF RCIAllocator, std,experimental,allocator) or
145 | $(REF RCISharedAllocator, std,experimental,allocator)
146 | allocator object
147 | s = input string
148 |
149 | Complexity: $(BIGOH m), where `m` is the number of bytes of the input string.
150 | */
151 | this()(string s)
152 | {
153 | import std.string : representation;
154 | this(defaultAllocator!(typeof(this)), s.dup.representation);
155 | }
156 |
157 | ///
158 | @safe unittest
159 | {
160 | import std.algorithm.comparison : equal;
161 | auto s = RCString("dlang");
162 | assert(s.by!char.equal("dlang"));
163 | }
164 |
165 | /// ditto
166 | this(this Q)(dstring s)
167 | {
168 | import std.utf : byChar;
169 | this(s.byChar);
170 | }
171 |
172 | ///
173 | @safe unittest
174 | {
175 | import std.algorithm.comparison : equal;
176 | auto s = RCString("dlang"d);
177 | assert(s.by!char.equal("dlang"));
178 | }
179 |
180 | /// ditto
181 | this(this Q)(wstring s)
182 | {
183 | import std.utf : byChar;
184 | this(s.byChar);
185 | }
186 |
187 | ///
188 | @safe unittest
189 | {
190 | import std.algorithm.comparison : equal;
191 | auto s = RCString("dlang"w);
192 | assert(s.by!char.equal("dlang"));
193 | }
194 |
195 | /**
196 | Constructs a qualified rcstring out of an input range
197 | that will use the provided allocator object.
198 | For `immutable` objects, a `RCISharedAllocator` must be supplied.
199 | If no allocator is passed, the default allocator will be used.
200 |
201 | Params:
202 | allocator = a $(REF RCIAllocator, std,experimental,allocator) or
203 | $(REF RCISharedAllocator, std,experimental,allocator)
204 | allocator object
205 | r = input range
206 |
207 | Complexity: $(BIGOH n), where `n` is the number of elemtns of the input range.
208 | */
209 | this(this Q, A, R)(A allocator, R r)
210 | if (!is(Q == shared)
211 | && (is(A == RCISharedAllocator) || !is(Q == immutable))
212 | && (is(A == RCIAllocator) || is(A == RCISharedAllocator))
213 | && isInputRange!R && isSomeChar!(ElementType!R) && !isSomeString!R)
214 | {
215 | import std.utf : byChar;
216 | this(allocator);
217 | static if (hasLength!R)
218 | _support.reserve(r.length);
219 | foreach (e; r.byChar)
220 | _support ~= cast(ubyte) e;
221 | }
222 |
223 | /// ditto
224 | this(this Q, R)(R r)
225 | if (isInputRange!R && isSomeChar!(ElementType!R) && !isSomeString!R)
226 | {
227 | this(defaultAllocator!(typeof(this)), r);
228 | }
229 |
230 | ///
231 | @safe unittest
232 | {
233 | import std.range : take;
234 | import std.utf : byCodeUnit;
235 | auto s = RCString("dlang".byCodeUnit.take(10));
236 | assert(s.equal("dlang"));
237 | }
238 |
239 | ///
240 | @nogc nothrow pure @safe
241 | bool empty() const
242 | {
243 | return _support.empty;
244 | }
245 |
246 | ///
247 | @safe unittest
248 | {
249 | assert(!RCString("dlang").empty);
250 | assert(RCString("").empty);
251 | }
252 |
253 | ///
254 | @trusted
255 | auto by(T)()
256 | if (is(T == char) || is(T == wchar) || is(T == dchar))
257 | {
258 | Array!char tmp = *cast(Array!char*)(&_support);
259 | static if (is(T == char))
260 | {
261 | return tmp;
262 | }
263 | else
264 | {
265 | import std.utf : byUTF;
266 | return tmp.byUTF!T();
267 | }
268 | }
269 |
270 | ///
271 | @safe unittest
272 | {
273 | import std.algorithm.comparison : equal;
274 | import std.utf : byChar, byWchar;
275 | auto hello = RCString("你好");
276 | assert(hello.by!char.equal("你好".byChar));
277 | assert(hello.by!wchar.equal("你好".byWchar));
278 | assert(hello.by!dchar.equal("你好"));
279 | }
280 |
281 | ///
282 | typeof(this) opBinary(string op)(typeof(this) rhs)
283 | if (op == "~")
284 | {
285 | RCString s = this;
286 | s._support ~= rhs._support;
287 | return s;
288 | }
289 |
290 | /// ditto
291 | typeof(this) opBinary(string op)(string rhs)
292 | if (op == "~")
293 | {
294 | auto rcs = RCString(rhs);
295 | RCString s = this;
296 | s._support ~= rcs._support;
297 | return s;
298 | }
299 |
300 | /// ditto
301 | typeof(this) opBinaryRight(string op)(string rhs)
302 | if (op == "~")
303 | {
304 | auto s = RCString(rhs);
305 | RCString rcs = this;
306 | s._support ~= rcs._support;
307 | return s;
308 | }
309 |
310 | /// ditto
311 | typeof(this) opBinary(string op, C)(C c)
312 | if (op == "~" && isSomeChar!C)
313 | {
314 | RCString s = this;
315 | s._support ~= cast(ubyte) c;
316 | return s;
317 | }
318 |
319 | /// ditto
320 | typeof(this) opBinaryRight(string op, C)(C c)
321 | if (op == "~" && isSomeChar!C)
322 | {
323 | RCString rcs = this;
324 | rcs._support.insert(0, cast(ubyte) c);
325 | return rcs;
326 | }
327 |
328 | /// ditto
329 | typeof(this) opBinary(string op, R)(R r)
330 | if (op == "~" && isInputRange!R && isSomeChar!(ElementType!R) && !isSomeString!R)
331 | {
332 | RCString s = this;
333 | static if (hasLength!R)
334 | s._support.reserve(s._support.length + r.length);
335 | foreach (el; r)
336 | {
337 | s._support ~= cast(ubyte) el;
338 | }
339 | return s;
340 | }
341 |
342 | /// ditto
343 | typeof(this) opBinaryRight(string op, R)(R lhs)
344 | if (op == "~" && isInputRange!R && isSomeChar!(ElementType!R) && !isSomeString!R)
345 | {
346 | auto l = RCString(lhs);
347 | RCString rcs = this;
348 | l._support ~= rcs._support;
349 | return l;
350 | }
351 |
352 | ///
353 | @safe unittest
354 | {
355 | auto r1 = RCString("abc");
356 | auto r2 = RCString("def");
357 | assert((r1 ~ r2).equal("abcdef"));
358 | assert((r1 ~ "foo").equal("abcfoo"));
359 | assert(("abc" ~ r2).equal("abcdef"));
360 | assert((r1 ~ 'd').equal("abcd"));
361 | assert(('a' ~ r2).equal("adef"));
362 | }
363 |
364 | ///
365 | @safe unittest
366 | {
367 | import std.range : take;
368 | import std.utf : byCodeUnit;
369 | auto r1 = RCString("abc");
370 | auto r2 = "def".byCodeUnit.take(3);
371 | assert((r1 ~ r2).equal("abcdef"));
372 | assert((r2 ~ r1).equal("defabc"));
373 | }
374 |
375 | ///
376 | auto opBinary(string op)(typeof(this) rhs)
377 | if (op == "in")
378 | {
379 | // TODO
380 | import std.algorithm.searching : find;
381 | return this.by!char.find(rhs.by!char);
382 | }
383 |
384 | auto opBinaryRight(string op)(string rhs)
385 | if (op == "in")
386 | {
387 | // TODO
388 | import std.algorithm.searching : find;
389 | return rhs.find(this.by!char);
390 | }
391 |
392 | ///
393 | @safe unittest
394 | {
395 | auto r1 = RCString("abc");
396 | auto r2 = RCString("def");
397 | auto rtext = RCString("abcdefgh");
398 | //import std.stdio;
399 | //(r1 in rtext).writeln;
400 | //(r1 in rtext).writeln;
401 | }
402 |
403 | ///
404 | typeof(this) opOpAssign(string op)(typeof(this) rhs)
405 | if (op == "~")
406 | {
407 | _support ~= rhs._support;
408 | return this;
409 | }
410 |
411 | ///
412 | @safe unittest
413 | {
414 | auto r1 = RCString("abc");
415 | r1 ~= RCString("def");
416 | assert(r1.equal("abcdef"));
417 | }
418 |
419 | /// ditto
420 | typeof(this) opOpAssign(string op)(string rhs)
421 | if (op == "~")
422 | {
423 | import std.string : representation;
424 | _support ~= rhs.representation;
425 | return this;
426 | }
427 |
428 | ///
429 | @safe unittest
430 | {
431 | auto r1 = RCString("abc");
432 | r1 ~= "def";
433 | assert(r1.equal("abcdef"));
434 | }
435 |
436 | typeof(this) opOpAssign(string op, C)(C c)
437 | if (op == "~" && isSomeChar!C)
438 | {
439 | _support ~= cast(ubyte) c;
440 | return this;
441 | }
442 |
443 | ///
444 | @safe unittest
445 | {
446 | auto r1 = RCString("abc");
447 | r1 ~= 'd';
448 | assert(r1.equal("abcd"));
449 | }
450 |
451 | typeof(this) opOpAssign(string op, R)(R r)
452 | if (op == "~" && isSomeChar!(ElementType!R) && isInputRange!R && !isSomeString!R)
453 | {
454 | _support ~= RCString(r)._support;
455 | return this;
456 | }
457 |
458 | ///
459 | @safe unittest
460 | {
461 | import std.range : take;
462 | import std.utf : byCodeUnit;
463 | auto r1 = RCString("abc");
464 | r1 ~= "foo".byCodeUnit.take(4);
465 | assert(r1.equal("abcfoo"));
466 | }
467 |
468 | ///
469 | bool opEquals()(auto ref typeof(this) rhs) const
470 | {
471 | return _support == rhs._support;
472 | }
473 |
474 | ///
475 | @safe unittest
476 | {
477 | assert(RCString("abc") == RCString("abc"));
478 | assert(RCString("abc") != RCString("Abc"));
479 | assert(RCString("abc") != RCString("abd"));
480 | assert(RCString("abc") != RCString(""));
481 | assert(RCString("") == RCString(""));
482 | }
483 |
484 | /// ditto
485 | bool opEquals()(string rhs) const
486 | {
487 | import std.string : representation;
488 | import std.algorithm.comparison : equal;
489 | return _support._payload.equal(rhs.representation);
490 | }
491 |
492 | ///
493 | @safe unittest
494 | {
495 | assert(RCString("abc") == "abc");
496 | assert(RCString("abc") != "Abc");
497 | assert(RCString("abc") != "abd");
498 | assert(RCString("abc") != "");
499 | assert(RCString("") == "");
500 | }
501 |
502 | bool opEquals(R)(R r)
503 | if (isSomeChar!(ElementType!R) && isInputRange!R && !isSomeString!R)
504 | {
505 | import std.algorithm.comparison : equal;
506 | return _support.equal(r);
507 | }
508 |
509 | ///
510 | @safe unittest
511 | {
512 | import std.range : take;
513 | import std.utf : byCodeUnit;
514 | assert(RCString("abc") == "abc".byCodeUnit.take(3));
515 | assert(RCString("abc") != "Abc".byCodeUnit.take(3));
516 | assert(RCString("abc") != "abd".byCodeUnit.take(3));
517 | assert(RCString("abc") != "".byCodeUnit.take(3));
518 | assert(RCString("") == "".byCodeUnit.take(3));
519 | }
520 |
521 | ///
522 | int opCmp()(auto ref typeof(this) rhs)
523 | {
524 | return _support.opCmp(rhs._support);
525 | }
526 |
527 | ///
528 | @safe unittest
529 | {
530 | assert(RCString("abc") <= RCString("abc"));
531 | assert(RCString("abc") >= RCString("abc"));
532 | assert(RCString("abc") > RCString("Abc"));
533 | assert(RCString("Abc") < RCString("abc"));
534 | assert(RCString("abc") < RCString("abd"));
535 | assert(RCString("abc") > RCString(""));
536 | assert(RCString("") <= RCString(""));
537 | assert(RCString("") >= RCString(""));
538 | }
539 |
540 | int opCmp()(string rhs)
541 | {
542 | import std.string : representation;
543 | return _support.opCmp(rhs.representation);
544 | }
545 |
546 | ///
547 | @safe unittest
548 | {
549 | assert(RCString("abc") <= "abc");
550 | assert(RCString("abc") >= "abc");
551 | assert(RCString("abc") > "Abc");
552 | assert(RCString("Abc") < "abc");
553 | assert(RCString("abc") < "abd");
554 | assert(RCString("abc") > "");
555 | assert(RCString("") <= "");
556 | assert(RCString("") >= "");
557 | }
558 |
559 | int opCmp(R)(R rhs)
560 | if (isSomeChar!(ElementType!R) && isInputRange!R && !isSomeString!R)
561 | {
562 | import std.string : representation;
563 | return _support.opCmp(rhs);
564 | }
565 |
566 | ///
567 | @safe unittest
568 | {
569 | import std.range : take;
570 | import std.utf : byCodeUnit;
571 | assert(RCString("abc") <= "abc".byCodeUnit.take(3));
572 | assert(RCString("abc") >= "abc".byCodeUnit.take(3));
573 | assert(RCString("abc") > "Abc".byCodeUnit.take(3));
574 | assert(RCString("Abc") < "abc".byCodeUnit.take(3));
575 | assert(RCString("abc") < "abd".byCodeUnit.take(3));
576 | assert(RCString("abc") > "".byCodeUnit.take(3));
577 | assert(RCString("") <= "".byCodeUnit.take(3));
578 | assert(RCString("") >= "".byCodeUnit.take(3));
579 | }
580 |
581 | auto opSlice(size_t start, size_t end)
582 | {
583 | RCString s = save;
584 | s._support = s._support[start .. end];
585 | return s;
586 | }
587 |
588 | ///
589 | @safe unittest
590 | {
591 | auto a = RCString("abcdef");
592 | assert(a[2 .. $].equal("cdef"));
593 | assert(a[0 .. 2].equal("ab"));
594 | assert(a[3 .. $ - 1].equal("de"));
595 | }
596 |
597 | ///
598 | auto opDollar()
599 | {
600 | return _support.length;
601 | }
602 |
603 | ///
604 | auto save()
605 | {
606 | RCString s = this;
607 | return s;
608 | }
609 |
610 | ///
611 | auto opSlice()
612 | {
613 | return this.save;
614 | }
615 |
616 | // Phobos
617 | auto equal(T)(T rhs)
618 | {
619 | import std.algorithm.comparison : equal;
620 | return by!char.equal(rhs);
621 | }
622 |
623 | auto writeln(T...)(T rhs)
624 | {
625 | import std.stdio : writeln;
626 | return by!char.writeln(rhs);
627 | }
628 |
629 | string toString()
630 | {
631 | import std.array : array;
632 | import std.exception : assumeUnique;
633 | return by!char.array.assumeUnique;
634 | }
635 |
636 | ///
637 | auto opSliceAssign(char c, size_t start, size_t end)
638 | {
639 | _support[start .. end] = cast(ubyte) c;
640 | }
641 |
642 | ///
643 | @safe unittest
644 | {
645 | auto r1 = RCString("abcdef");
646 | r1[2..4] = '0';
647 | assert(r1.equal("ab00ef"));
648 | }
649 |
650 | ///
651 | bool opCast(T : bool)()
652 | {
653 | return !empty;
654 | }
655 |
656 | ///
657 | @safe unittest
658 | {
659 | assert(RCString("foo"));
660 | assert(!RCString(""));
661 | }
662 |
663 | /// ditto
664 | auto ref opAssign()(RCString rhs)
665 | {
666 | _support = rhs._support;
667 | return this;
668 | }
669 |
670 | /// ditto
671 | auto ref opAssign(R)(R rhs)
672 | {
673 | _support = RCString(rhs)._support;
674 | return this;
675 | }
676 |
677 | ///
678 | @safe unittest
679 | {
680 | auto rc = RCString("foo");
681 | assert(rc.equal("foo"));
682 | rc = RCString("bar1");
683 | assert(rc.equal("bar1"));
684 | rc = "bar2";
685 | assert(rc.equal("bar2"));
686 |
687 | import std.range : take;
688 | import std.utf : byCodeUnit;
689 | rc = "bar3".take(10).byCodeUnit;
690 | assert(rc.equal("bar3"));
691 | }
692 |
693 | auto dup()()
694 | {
695 | return RCString(by!char);
696 | }
697 |
698 | ///
699 | @safe unittest
700 | {
701 | auto s = RCString("foo");
702 | s = RCString("bar");
703 | assert(s.equal("bar"));
704 | auto s2 = s.dup;
705 | s2 = RCString("fefe");
706 | assert(s.equal("bar"));
707 | assert(s2.equal("fefe"));
708 | }
709 |
710 | auto idup()()
711 | {
712 | return RCString!(immutable(char))(by!char);
713 | }
714 |
715 | ///
716 | @safe unittest
717 | {
718 | auto s = RCString("foo");
719 | s = RCString("bar");
720 | assert(s.equal("bar"));
721 | auto s2 = s.dup;
722 | s2 = RCString("fefe");
723 | assert(s.equal("bar"));
724 | assert(s2.equal("fefe"));
725 | }
726 |
727 | ///
728 | auto opIndex(size_t pos)
729 | in
730 | {
731 | assert(pos < _support.length, "Invalid position.");
732 | }
733 | body
734 | {
735 | return _support[pos];
736 | }
737 |
738 | ///
739 | @safe unittest
740 | {
741 | auto s = RCString("bar");
742 | assert(s[0] == 'b');
743 | assert(s[1] == 'a');
744 | assert(s[2] == 'r');
745 | }
746 |
747 | ///
748 | auto opIndexAssign(char el, size_t pos)
749 | in
750 | {
751 | assert(pos < _support.length, "Invalid position.");
752 | }
753 | body
754 | {
755 | return _support[pos] = cast(ubyte) el;
756 | }
757 |
758 | ///
759 | @safe unittest
760 | {
761 | auto s = RCString("bar");
762 | assert(s[0] == 'b');
763 | s[0] = 'f';
764 | assert(s.equal("far"));
765 | }
766 |
767 | ///
768 | auto opIndexAssign(char c)
769 | {
770 | _support[] = cast(ubyte) c;
771 | }
772 |
773 | ///
774 | auto toHash()
775 | {
776 | // will be safe with 2.082
777 | return () @trusted { return _support.hashOf; }();
778 | }
779 |
780 | ///
781 | @safe unittest
782 | {
783 | auto rc = RCString("abc");
784 | assert(rc.toHash == RCString("abc").toHash);
785 | rc ~= 'd';
786 | assert(rc.toHash == RCString("abcd").toHash);
787 | assert(RCString().toHash == RCString().toHash);
788 | }
789 | }
790 |
791 | @safe unittest
792 | {
793 | import std.algorithm.comparison : equal;
794 |
795 | auto buf = cast(ubyte[])("aaa".dup);
796 | auto s = RCString(buf);
797 |
798 | assert(equal(s.by!char, "aaa"));
799 | s.by!char.front = 'b';
800 | assert(equal(s.by!char, "baa"));
801 | }
802 |
803 | @safe unittest
804 | {
805 | import std.algorithm.comparison : equal;
806 |
807 | auto buf = cast(ubyte[])("hell\u00F6".dup);
808 | auto s = RCString(buf);
809 |
810 | assert(s.by!char().equal(['h', 'e', 'l', 'l', 0xC3, 0xB6]));
811 |
812 | // `wchar`s are able to hold the ö in a single element (UTF-16 code unit)
813 | assert(s.by!wchar().equal(['h', 'e', 'l', 'l', 'ö']));
814 | }
815 |
816 | @safe unittest
817 | {
818 | import std.algorithm.comparison : equal;
819 |
820 | auto buf = cast(ubyte[])("hello".dup);
821 | auto s = RCString(buf);
822 | auto charStr = s.by!char;
823 |
824 | charStr[$ - 2] = cast(ubyte) 0xC3;
825 | charStr[$ - 1] = cast(ubyte) 0xB6;
826 |
827 | assert(s.by!wchar().equal(['h', 'e', 'l', 'ö']));
828 | }
829 |
--------------------------------------------------------------------------------
/source/stdx/collections/hashtable.d:
--------------------------------------------------------------------------------
1 | ///
2 | module stdx.collections.hashtable;
3 |
4 | import stdx.collections.common;
5 |
6 | debug(CollectionHashtable) import std.stdio;
7 |
8 | version(unittest)
9 | {
10 | import std.experimental.allocator.mallocator;
11 | import std.experimental.allocator.building_blocks.stats_collector;
12 | import std.experimental.allocator : allocatorObject,
13 | RCIAllocator, RCISharedAllocator;
14 |
15 | private alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed);
16 | }
17 |
18 | ///
19 | struct Hashtable(K, V)
20 | {
21 | import std.experimental.allocator : RCIAllocator, RCISharedAllocator;
22 | import std.traits : isImplicitlyConvertible;
23 | import std.typecons : Tuple, Nullable;
24 | import std.algorithm.mutation : move;
25 | import stdx.collections.slist : SList;
26 | import stdx.collections.array : Array;
27 |
28 | private:
29 | alias KVPair = Tuple!(K, "key", V, "value");
30 | Array!(SList!(KVPair)) _buckets;
31 | Array!size_t _numElems; // This needs to be ref counted
32 | static enum double loadFactor = 0.75;
33 | AllocatorHandler _allocator;
34 |
35 | // Allocators internals
36 |
37 | /// Constructs the ouroboros allocator from allocator if the ouroboros
38 | // allocator wasn't previously set
39 | /*@nogc*/ nothrow pure @safe
40 | bool setAllocator(A)(ref A allocator)
41 | if (is(A == RCIAllocator) || is(A == RCISharedAllocator))
42 | {
43 | if (_allocator.isNull)
44 | {
45 | auto a = typeof(_allocator)(allocator);
46 | move(a, _allocator);
47 |
48 | _buckets = Array!(SList!(KVPair))(allocator, SList!(KVPair)(allocator));
49 | //_buckets = Array!(SList!(KVPair))(allocator);
50 | _numElems = Array!size_t(allocator, 0UL);
51 |
52 | return true;
53 | }
54 | return false;
55 | }
56 |
57 | public:
58 | /**
59 | * Constructs a qualified hashtable that will use the provided
60 | * allocator object. For `immutable` objects, a `RCISharedAllocator` must
61 | * be supplied.
62 | *
63 | * Params:
64 | * allocator = a $(REF RCIAllocator, std,experimental,allocator) or
65 | * $(REF RCISharedAllocator, std,experimental,allocator)
66 | * allocator object
67 | *
68 | * Complexity: $(BIGOH 1)
69 | */
70 | this(A, this Q)(A allocator)
71 | if (!is(Q == shared)
72 | && (is(A == RCISharedAllocator) || !is(Q == immutable))
73 | && (is(A == RCIAllocator) || is(A == RCISharedAllocator)))
74 | {
75 | debug(CollectionHashtable)
76 | {
77 | writefln("Array.ctor: begin");
78 | scope(exit) writefln("Array.ctor: end");
79 | }
80 | static if (is(Q == immutable) || is(Q == const))
81 | {
82 | V[K] empty;
83 | this(allocator, empty);
84 | }
85 | else
86 | {
87 | setAllocator(allocator);
88 | }
89 | }
90 |
91 | ///
92 | static if (is(K == int))
93 | @safe unittest
94 | {
95 | import std.experimental.allocator : theAllocator, processAllocator;
96 |
97 | auto h = Hashtable!(int, int)(theAllocator);
98 | auto ch = const Hashtable!(int, int)(processAllocator);
99 | auto ih = immutable Hashtable!(int, int)(processAllocator);
100 | }
101 |
102 | /**
103 | * Constructs a qualified hashtable out of an associative array.
104 | * Because no allocator was provided, the hashtable will use the
105 | * $(REF, GCAllocator, std,experimental,allocator,gc_allocator).
106 | *
107 | * Params:
108 | * assocArr = an associative array
109 | *
110 | * Complexity: $(BIGOH m), where `m` is the number of (key, value) pairs
111 | * in the associative array.
112 | */
113 | this(U, this Q)(U assocArr)
114 | if (is(U == Value[Key], Value : V, Key : K))
115 | {
116 | this(defaultAllocator!(typeof(this)), assocArr);
117 | }
118 |
119 | ///
120 | static if (is(K == int))
121 | @safe unittest
122 | {
123 | import std.algorithm.comparison : equal;
124 |
125 | auto h = Hashtable!(int, int)([1 : 10]);
126 | assert(equal(h.keys(), [1]));
127 | assert(equal(h.values(), [10]));
128 | }
129 |
130 | /**
131 | * Constructs a qualified hashtable out of an associative array
132 | * that will use the provided allocator object.
133 | * For `immutable` objects, a `RCISharedAllocator` must be supplied.
134 | *
135 | * Params:
136 | * allocator = a $(REF RCIAllocator, std,experimental,allocator) or
137 | * $(REF RCISharedAllocator, std,experimental,allocator)
138 | * allocator object
139 | * assocArr = an associative array
140 | *
141 | * Complexity: $(BIGOH m), where `m` is the number of (key, value) pairs
142 | * in the associative array.
143 | */
144 | this(A, U, this Q)(A allocator, U assocArr)
145 | if (!is(Q == shared)
146 | && (is(A == RCISharedAllocator) || !is(Q == immutable))
147 | && (is(A == RCIAllocator) || is(A == RCISharedAllocator))
148 | && is(U == Value[Key], Value : V, Key : K))
149 | {
150 | debug(CollectionHashtable)
151 | {
152 | writefln("Hashtable.ctor: begin");
153 | scope(exit) writefln("Hashtable.ctor: end");
154 | }
155 | static if (is(Q == immutable) || is(Q == const))
156 | {
157 | // Build a mutable hashtable on the stack and pass ownership to this
158 |
159 | // TODO: is this ok?
160 | auto tmp = Hashtable!(K, V)(assocArr);
161 | //_buckets = cast(typeof(_buckets))(tmp._buckets);
162 | _buckets = typeof(_buckets)(allocator, tmp._buckets);
163 | //_numElems = cast(typeof(_numElems))(tmp._numElems);
164 | _numElems = typeof(_numElems)(allocator, tmp._numElems);
165 |
166 | _allocator = immutable AllocatorHandler(allocator);
167 | }
168 | else
169 | {
170 | setAllocator(allocator);
171 | insert(assocArr);
172 | }
173 | }
174 |
175 | ///
176 | static if (is(K == int))
177 | @safe unittest
178 | {
179 | import std.algorithm.comparison : equal;
180 | import stdx.collections.array : Array;
181 |
182 | {
183 | auto h = Hashtable!(int, int)([1 : 10]);
184 | assert(equal(h.keys(), [1]));
185 | assert(equal(h.values(), [10]));
186 | }
187 |
188 | {
189 | auto h = immutable Hashtable!(int, int)([1 : 10]);
190 | assert(equal(h.values(), Array!int([10])));
191 | }
192 | }
193 |
194 | static if (is(K == int))
195 | /*nothrow*/ pure @safe unittest
196 | {
197 | auto h = Hashtable!(int, int)([1 : 42]);
198 |
199 | // Infer safety
200 | static assert(!__traits(compiles, () @safe { Hashtable!(Unsafe, int)([Unsafe(1) : 1]); }));
201 | static assert(!__traits(compiles, () @safe { auto s = const Hashtable!(Unsafe, int)([Unsafe(1) : 1]); }));
202 | static assert(!__traits(compiles, () @safe { auto s = immutable Hashtable!(Unsafe, int)([Unsafe(1) : 1]); }));
203 |
204 | static assert(!__traits(compiles, () @safe { Hashtable!(UnsafeDtor, int)([UnsafeDtor(1) : 1]); }));
205 | static assert(!__traits(compiles, () @safe { auto s = const Hashtable!(UnsafeDtor, int)([UnsafeDtor(1) : 1]); }));
206 | static assert(!__traits(compiles, () @safe { auto s = immutable Hashtable!(UnsafeDtor, int)([UnsafeDtor(1) : 1]); }));
207 |
208 | // Infer purity
209 | static assert(!__traits(compiles, () @safe { Hashtable!(Impure, int)([Impure(1) : 1]); }));
210 | static assert(!__traits(compiles, () @safe { auto s = const Hashtable!(Impure, int)([Impure(1) : 1]); }));
211 | static assert(!__traits(compiles, () @safe { auto s = immutable Hashtable!(Impure, int)([Impure(1) : 1]); }));
212 |
213 | static assert(!__traits(compiles, () @safe { Hashtable!(ImpureDtor, int)([ImpureDtor(1) : 1]); }));
214 | static assert(!__traits(compiles, () @safe { auto s = const Hashtable!(ImpureDtor, int)([ImpureDtor(1) : 1]); }));
215 | static assert(!__traits(compiles, () @safe { auto s = immutable Hashtable!(ImpureDtor, int)([ImpureDtor(1) : 1]); }));
216 | }
217 |
218 | /**
219 | * Insert the (key, value) pairs of an associative array into the
220 | * hashtable.
221 | *
222 | * If no allocator was provided when the list was created, the
223 | * $(REF, GCAllocator, std,experimental,allocator,gc_allocator) will be used.
224 | *
225 | * Params:
226 | * assocArr = an associative array
227 | *
228 | * Complexity: $(BIGOH m), where `m` is the number of (key, value) pairs
229 | * in the associative array.
230 | */
231 | size_t insert(U)(U assocArr)
232 | if (is(U == Value[Key], Value : V, Key : K))
233 | {
234 | debug(CollectionHashtable)
235 | {
236 | writefln("Hashtable.insert: begin");
237 | scope(exit) writefln("Hashtable.insert: end");
238 | }
239 |
240 | // Will be optimized away, but the type system infers T's safety
241 | if (0) { K t = K.init; V v = V.init; }
242 |
243 | // Ensure we have an allocator. If it was already set, this will do nothing
244 | auto a = threadAllocatorObject();
245 | setAllocator(a);
246 |
247 | if (_buckets.empty)
248 | {
249 | auto reqCap = requiredCapacity(assocArr.length);
250 | _buckets.reserve(reqCap);
251 | _buckets.forceLength(reqCap);
252 | _numElems.reserve(1);
253 | _numElems.forceLength(1);
254 | }
255 | foreach(k, v; assocArr)
256 | {
257 | size_t pos = k.hashOf & (_buckets.length - 1);
258 | if (_buckets[pos].empty)
259 | {
260 | _buckets[pos] = SList!(KVPair)(_allocator.getLocalAlloc, KVPair(k, v));
261 | }
262 | else
263 | {
264 | _buckets[pos].insert(0, KVPair(k, v));
265 | }
266 | }
267 | _numElems[0] += assocArr.length;
268 | return assocArr.length;
269 | }
270 |
271 | ///
272 | static if (is(K == int))
273 | @safe unittest
274 | {
275 | import std.algorithm.comparison : equal;
276 |
277 | auto h = Hashtable!(int, int)();
278 | assert(h.length == 0);
279 | h.insert([1 : 10]);
280 | assert(equal(h.keys(), [1]));
281 | assert(equal(h.values(), [10]));
282 | }
283 |
284 | private @nogc nothrow pure @safe
285 | size_t requiredCapacity(size_t numElems)
286 | {
287 | static enum size_t maxPow2 = cast(size_t)(1) << (size_t.sizeof * 8 - 1);
288 | while (numElems & (numElems - 1))
289 | {
290 | numElems &= (numElems - 1);
291 | }
292 | return numElems < maxPow2 ? 2 * numElems : maxPow2;
293 | }
294 |
295 | /**
296 | * Returns number of key-value pairs.
297 | *
298 | * Returns:
299 | * a positive integer.
300 | *
301 | * Complexity: $(BIGOH 1).
302 | */
303 | @nogc nothrow pure @safe
304 | size_t length() const
305 | {
306 | return _numElems.empty ? 0 : _numElems[0];
307 | }
308 |
309 | ///
310 | static if (is(K == int))
311 | @safe unittest
312 | {
313 | import std.algorithm.comparison : equal;
314 |
315 | auto h = Hashtable!(int, int)();
316 | assert(h.length == 0);
317 | h.insert([1 : 10]);
318 | assert(h.length == 1);
319 | }
320 |
321 | /**
322 | * Returns number of buckets.
323 | *
324 | * Returns:
325 | * a positive integer.
326 | *
327 | * Complexity: $(BIGOH 1).
328 | */
329 | @nogc nothrow pure @safe
330 | size_t size() const
331 | {
332 | return _buckets.length;
333 | }
334 |
335 | /**
336 | * Check if the hashtable is empty.
337 | *
338 | * Returns:
339 | * `true` if there are no elements in the hashtable; `false` otherwise.
340 | *
341 | * Complexity: $(BIGOH 1).
342 | */
343 | @nogc nothrow pure @safe
344 | bool empty() const
345 | {
346 | return _buckets.empty || _numElems.empty || _numElems[0] == 0;
347 | }
348 |
349 | /**
350 | * Provide access to the first value in the hashtable. The user must check
351 | * that the hashtable isn't `empty`, prior to calling this function.
352 | * There is no guarantee of the order of the values, as they are placed in
353 | * the hashtable based on the result of the hash function.
354 | *
355 | * Returns:
356 | * a reference to the first found value.
357 | *
358 | * Complexity: $(BIGOH length).
359 | */
360 | ref auto front(this _)()
361 | {
362 | debug(CollectionHashtable)
363 | {
364 | writefln("Hashtable.front: begin");
365 | scope(exit) writefln("Hashtable.front: end");
366 | }
367 | auto tmpBuckets = _buckets;
368 | while ((!tmpBuckets.empty) && tmpBuckets.front.empty)
369 | {
370 | tmpBuckets.popFront;
371 | }
372 | assert(!tmpBuckets.empty, "Hashtable.front: Hashtable is empty");
373 | return tmpBuckets.front.front;
374 | }
375 |
376 | void popFront()
377 | {
378 | debug(CollectionHashtable)
379 | {
380 | writefln("Hashtable.popFront: begin");
381 | scope(exit) writefln("Hashtable.popFront: end");
382 | }
383 | if (!_buckets.isUnique)
384 | {
385 | _buckets = _buckets.dup();
386 | _numElems = _numElems.dup();
387 | }
388 | while ((!_buckets.empty) && _buckets.front.empty)
389 | {
390 | _buckets.popFront;
391 | }
392 | assert(!_buckets.empty, "Hashtable.front: Hashtable is empty");
393 | _buckets.front.popFront;
394 | --_numElems[0];
395 | // Find the next non-empty bucketList
396 | if (_buckets.front.empty)
397 | {
398 | while ((!_buckets.empty) && _buckets.front.empty)
399 | {
400 | _buckets.popFront;
401 | }
402 | }
403 | }
404 |
405 | private const
406 | void getKeyValues(string kv, BuckQual, ListQual, ArrQual)(BuckQual b, ListQual l, ref ArrQual values)
407 | {
408 | if (b.empty)
409 | {
410 | return;
411 | }
412 | else if (l.empty)
413 | {
414 | auto bTail = b.tail;
415 | if (!bTail.empty)
416 | {
417 | getKeyValues!kv(bTail, bTail.front, values);
418 | }
419 | }
420 | else
421 | {
422 | static if (kv.length)
423 | {
424 | mixin("values ~= l.front." ~ kv ~ ";");
425 | }
426 | else
427 | {
428 | mixin("values ~= l.front;");
429 | }
430 | getKeyValues!kv(b, l.tail, values);
431 | }
432 | }
433 |
434 | /**
435 | * Get an array with the existing keys in the hashtable.
436 | *
437 | * Returns:
438 | * an `Array!K` array of keys
439 | *
440 | * Complexity: $(BIGOH size).
441 | */
442 | Array!K keys(this _)()
443 | {
444 | debug(CollectionHashtable)
445 | {
446 | writefln("Hashtable.keys: begin");
447 | scope(exit) writefln("Hashtable.keys: end");
448 | }
449 |
450 | Array!K keys;
451 | auto tmp = _buckets;
452 | if (!_buckets.empty)
453 | //if (!tmp.empty)
454 | {
455 | //getKeyValues!("key")(tmp, tmp.front, keys);
456 | getKeyValues!("key")(_buckets, _buckets.front, keys);
457 | }
458 | return keys;
459 | }
460 |
461 | /**
462 | * Get an array with the existing values in the hashtable.
463 | *
464 | * Returns:
465 | * an `Array!V` array of values
466 | *
467 | * Complexity: $(BIGOH length).
468 | */
469 | Array!V values(this _)()
470 | {
471 | debug(CollectionHashtable)
472 | {
473 | writefln("Hashtable.values: begin");
474 | scope(exit) writefln("Hashtable.values: end");
475 | }
476 |
477 | Array!V values;
478 | if (!_buckets.empty)
479 | {
480 | getKeyValues!("value")(_buckets, _buckets.front, values);
481 | }
482 |
483 | return values;
484 | }
485 |
486 | /**
487 | * Get an array with the existing key-value pairs in the hashtable.
488 | *
489 | * Returns:
490 | * an `Array!(Tuple!(K, "key", V, "value"))` array of key-value pairs
491 | *
492 | * Complexity: $(BIGOH length).
493 | */
494 | Array!(Tuple!(K, "key", V, "value")) keyValuePairs(this _)()
495 | {
496 | debug(CollectionHashtable)
497 | {
498 | writefln("Hashtable.keyValuePairs: begin");
499 | scope(exit) writefln("Hashtable.keyValuePairs: end");
500 | }
501 |
502 | Array!KVPair pairs;
503 | if (!_buckets.empty)
504 | {
505 | getKeyValues!("")(_buckets, _buckets.front, pairs);
506 | }
507 |
508 | return pairs;
509 | }
510 |
511 | private const
512 | Nullable!V getValue(ListQual)(ListQual list, ref K key)
513 | {
514 | if (list.empty)
515 | {
516 | return Nullable!V.init;
517 | }
518 | else if (list.front.key == key)
519 | {
520 | return Nullable!V(list.front.value);
521 | }
522 | else
523 | {
524 | return getValue(list.tail, key);
525 | }
526 | }
527 |
528 | /**
529 | * Get the value corresponding to the given `key`; if it doesn't exist
530 | * return `nullValue`.
531 | *
532 | * Params:
533 | * key = the key corresponding to a value
534 | * nullValue = a value that will be returned if there is no such
535 | * key-value pair
536 | *
537 | * Returns:
538 | * the corresponding key value, or `nullValue`.
539 | *
540 | * Complexity: $(BIGOH n), where n is the number of elements in the
541 | * corresponding key bucket.
542 | */
543 | V get(this _)(K key, V nullValue)
544 | {
545 | debug(CollectionHashtable)
546 | {
547 | writefln("Hashtable.get: begin");
548 | scope(exit) writefln("Hashtable.get: end");
549 | }
550 |
551 | size_t pos = key.hashOf & (_buckets.length - 1);
552 | auto result = getValue(_buckets[pos], key);
553 | if (!result.isNull)
554 | {
555 | return result.get;
556 | }
557 | return nullValue;
558 | }
559 |
560 | /**
561 | * Get a `Nullable!V` corresponding to the given `key` index.
562 | *
563 | * Params:
564 | * key = the key corresponding to a value
565 | *
566 | * Returns:
567 | * a nullable value
568 | *
569 | * Complexity: $(BIGOH n), where n is the number of elements in the
570 | * corresponding key bucket.
571 | */
572 | Nullable!V opIndex(this _)(K key)
573 | {
574 | debug(CollectionHashtable)
575 | {
576 | writefln("Hashtable.opIndex: begin");
577 | scope(exit) writefln("Hashtable.opIndex: end");
578 | }
579 |
580 | size_t pos = key.hashOf & (_buckets.length - 1);
581 | return getValue(_buckets[pos], key);
582 | }
583 |
584 | /**
585 | * Apply a unary operation to the value corresponding to `key`.
586 | * If there isn't such a value, return a `V.init` wrapped inside a
587 | * `Nullable`.
588 | *
589 | * Params:
590 | * key = the key corresponding to a value
591 | *
592 | * Returns:
593 | * a nullable value corresponding to the result or `V.init`.
594 | *
595 | * Complexity: $(BIGOH n), where n is the number of elements in the
596 | * corresponding key bucket.
597 | */
598 | Nullable!V opIndexUnary(string op)(K key)
599 | {
600 | debug(CollectionHashtable)
601 | {
602 | writefln("Hashtable.opIndexUnary!" ~ op ~ ": begin");
603 | scope(exit) writefln("Hashtable.opIndexUnary!" ~ op ~ ": end");
604 | }
605 |
606 | size_t pos = key.hashOf & (_buckets.length - 1);
607 | foreach(ref pair; _buckets[pos])
608 | {
609 | if (pair.key == key)
610 | {
611 | return Nullable!V(mixin(op ~ "pair.value"));
612 | }
613 | }
614 | return Nullable!V.init;
615 | }
616 |
617 | /**
618 | * Assign `val` to the element corresponding to `key`.
619 | * If there isn't such a value, return a `V.init` wrapped inside a
620 | * `Nullable`.
621 | *
622 | * Params:
623 | * val = the value to be set
624 | * key = the key corresponding to a value
625 | *
626 | * Returns:
627 | * a nullable value corresponding to the result or `V.init`.
628 | *
629 | * Complexity: $(BIGOH n), where n is the number of elements in the
630 | * corresponding key bucket.
631 | */
632 | Nullable!V opIndexAssign(U)(U val, K key)
633 | if (isImplicitlyConvertible!(U, V))
634 | {
635 | debug(CollectionHashtable)
636 | {
637 | writefln("Hashtable.opIndexAssign: begin");
638 | scope(exit) writefln("Hashtable.opIndexAssign: end");
639 | }
640 |
641 | size_t pos = key.hashOf & (_buckets.length - 1);
642 | foreach(ref pair; _buckets[pos])
643 | {
644 | if (pair.key == key)
645 | {
646 | return Nullable!V(pair.value);
647 | }
648 | }
649 | return Nullable!V.init;
650 | }
651 |
652 | /**
653 | * Assign to the element corresponding to `key` the result of
654 | * applying `op` to the current value.
655 | * If there isn't such a value, return a `V.init` wrapped inside a
656 | * `Nullable`.
657 | *
658 | * Params:
659 | * val = the value to be used with `op`
660 | * key = the key corresponding to a value
661 | *
662 | * Returns:
663 | * a nullable value corresponding to the result or `V.init`.
664 | *
665 | * Complexity: $(BIGOH n), where n is the number of elements in the
666 | * corresponding key bucket.
667 | */
668 | Nullable!V opIndexOpAssign(string op, U)(U val, K key)
669 | if (isImplicitlyConvertible!(U, V))
670 | {
671 | debug(CollectionHashtable)
672 | {
673 | writefln("Hashtable.opIndexOpAssign: begin");
674 | scope(exit) writefln("Hashtable.opIndexOpAssign: end");
675 | }
676 |
677 | size_t pos = key.hashOf & (_buckets.length - 1);
678 | foreach(ref pair; _buckets[pos])
679 | {
680 | if (pair.key == key)
681 | {
682 | return Nullable!V(mixin("pair.value" ~ op ~ "= val"));
683 | }
684 | }
685 | return Nullable!V.init;
686 | }
687 |
688 | auto ref opBinary(string op, U)(auto ref U rhs)
689 | if (op == "~" &&
690 | (is (U == typeof(this))
691 | || is (U : T)
692 | || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
693 | ));
694 |
695 | /**
696 | * Assign `rhs` to this hashtable. The current hashtable will now become
697 | * another reference to `rhs`, unless `rhs` is `null`, in which case the
698 | * current hashtable will become empty. If `rhs` refers to the current
699 | * hashtable nothing will happen.
700 | *
701 | * If there are no more references to the previous hashtable the previous
702 | * hashtable will be destroyed; this leads to a $(BIGOH length) complexity.
703 | *
704 | * Params:
705 | * rhs = a reference to a hashtable
706 | *
707 | * Returns:
708 | * a reference to this hashtable
709 | *
710 | * Complexity: $(BIGOH length).
711 | */
712 | auto ref opAssign()(auto ref typeof(this) rhs)
713 | {
714 | debug(CollectionHashtable)
715 | {
716 | writefln("Hashtable.opAssign: begin");
717 | scope(exit) writefln("Hashtable.opAssign: end");
718 | }
719 |
720 | _buckets = rhs._buckets;
721 | _numElems = rhs._numElems;
722 | _ouroborosAllocator = rhs._ouroborosAllocator;
723 | return this;
724 | }
725 |
726 | void remove(K key);
727 |
728 | /**
729 | * Removes all the elements in the current hashtable.
730 | *
731 | * Complexity: $(BIGOH length).
732 | */
733 | void clear()
734 | {
735 | debug(CollectionHashtable)
736 | {
737 | writefln("Hashtable.clear: begin");
738 | scope(exit) writefln("Hashtable.clear: end");
739 | }
740 | auto alloc = _allocator.getLocalAlloc;
741 | _buckets = Array!(SList!(KVPair))(alloc, SList!(KVPair)(alloc));
742 | _numElems = Array!size_t(alloc, 0UL);
743 | }
744 |
745 | void rehash();
746 | }
747 |
748 | version(unittest) private /*nothrow*/ pure @safe
749 | void testSimple(RCIAllocator allocator)
750 | {
751 | import std.algorithm.comparison : equal;
752 |
753 | auto h = Hashtable!(int, int)(allocator, [1 : 10]);
754 |
755 | assert(equal(h.keys(), [1]));
756 | assert(equal(h.values(), [10]));
757 | assert(h.get(1, -1) == 10);
758 | assert(h.get(200, -1) == -1);
759 | assert(--h[1] == 9);
760 | assert(h.get(1, -1) == 9);
761 | }
762 |
763 | @safe unittest
764 | {
765 | import std.conv;
766 | SCAlloc statsCollectorAlloc;
767 | {
768 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
769 | () /*nothrow pure*/ @safe {
770 | testSimple(_allocator);
771 | }();
772 | }
773 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
774 | assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
775 | ~ to!string(bytesUsed) ~ " bytes");
776 | }
777 |
778 | version(unittest) private /*nothrow*/ pure @safe
779 | void testIndex(RCIAllocator allocator)
780 | {
781 | import std.algorithm.comparison : equal;
782 | import std.typecons : Tuple;
783 |
784 | auto h = Hashtable!(int, int)();
785 | assert(h.length == 0);
786 | h.insert([1 : 10]);
787 | assert(h.length == 1);
788 | assert(h._buckets[0].front == Tuple!(int, int)(1, 10));
789 | h.clear();
790 | assert(h.length == 0);
791 | assert(h.empty);
792 |
793 | h.insert([1 : 10]);
794 | assert(equal(h.keys(), [1]));
795 | }
796 |
797 | @safe unittest
798 | {
799 | import std.conv;
800 | SCAlloc statsCollectorAlloc;
801 | {
802 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
803 | () /*nothrow pure*/ @safe {
804 | testIndex(_allocator);
805 | }();
806 | }
807 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
808 | assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
809 | ~ to!string(bytesUsed) ~ " bytes");
810 | }
811 |
812 | version(unittest) private /*nothrow*/ pure @safe
813 | void testSimpleImmutable(RCIAllocator allocator)
814 | {
815 | import std.algorithm.comparison : equal;
816 | import std.typecons : Tuple;
817 | import stdx.collections.array : Array;
818 |
819 | auto h = immutable Hashtable!(int, int)([1 : 10]);
820 | assert(h._buckets[0].front == Tuple!(int, int)(1, 10));
821 | assert(h.get(1, -1) == 10);
822 | assert(equal(h.values(), Array!int([10])));
823 | assert(equal(h.keyValuePairs(), Array!(Tuple!(int, int))(Tuple!(int, int)(1, 10))));
824 | }
825 |
826 | @safe unittest
827 | {
828 | import std.conv;
829 | SCAlloc statsCollectorAlloc;
830 | {
831 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
832 | () /*nothrow pure*/ @safe {
833 | testSimpleImmutable(_allocator);
834 | }();
835 | }
836 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
837 | assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
838 | ~ to!string(bytesUsed) ~ " bytes");
839 | }
840 |
--------------------------------------------------------------------------------
/source/stdx/collections/slist.d:
--------------------------------------------------------------------------------
1 | ///
2 | module stdx.collections.slist;
3 |
4 | import stdx.collections.common;
5 |
6 | debug(CollectionSList) import std.stdio;
7 |
8 | version(unittest)
9 | {
10 | import std.experimental.allocator.mallocator;
11 | import std.experimental.allocator.building_blocks.stats_collector;
12 | import std.experimental.allocator : RCIAllocator, RCISharedAllocator,
13 | allocatorObject, sharedAllocatorObject;
14 |
15 | private alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed);
16 | }
17 |
18 | ///
19 | struct SList(T)
20 | {
21 | import std.experimental.allocator : RCIAllocator, RCISharedAllocator,
22 | theAllocator, processAllocator, make, dispose, stateSize;
23 | import std.experimental.allocator.building_blocks.affix_allocator;
24 | import std.traits : isImplicitlyConvertible;
25 | import std.range.primitives : isInputRange, ElementType;
26 | import std.variant : Algebraic;
27 | import std.conv : emplace;
28 | import core.atomic : atomicOp;
29 | import std.algorithm.mutation : move;
30 |
31 | private:
32 | // TODO: should this be static struct?
33 | struct Node
34 | {
35 | T _payload;
36 | Node *_next;
37 |
38 | this(T v, Node *n)
39 | {
40 | debug(CollectionSList) writefln("SList.Node.ctor: Constructing node" ~
41 | " with payload: %s", v);
42 | _payload = v;
43 | _next = n;
44 | }
45 |
46 | ~this()
47 | {
48 | debug(CollectionSList) writefln("SList.Node.dtor: Destroying node" ~
49 | " with payload: %s", _payload);
50 | }
51 | }
52 |
53 | // State {
54 | Node *_head;
55 | mixin(allocatorHandler);
56 | // }
57 |
58 | @nogc nothrow pure @trusted
59 | void addRef(QualNode, this Q)(QualNode node)
60 | {
61 | assert(node !is null);
62 | cast(void) _allocator.opPrefix!("+=")(cast(void[Node.sizeof])(*node), 1);
63 | }
64 |
65 | void delRef(ref Node *node)
66 | {
67 | // Will be optimized away, but the type system infers T's safety
68 | if (0) { T t = T.init; }
69 |
70 | assert(node !is null);
71 | //if (_allocator.opPrefix!("-=")(cast(void[Node.sizeof])(*node), 1) == 0)
72 | //{
73 | //debug(CollectionSList) writefln("SList.delRef: Deleting node %s", node._payload);
74 | //dispose(_allocator, node);
75 | //node = null;
76 | //}
77 | () @trusted {
78 | if (opCmpPrefix!"=="(node, 0))
79 | {
80 | dispose(_allocator, node);
81 | node = null;
82 | }
83 | else
84 | {
85 | cast(void) _allocator.opPrefix!("-=")(cast(void[Node.sizeof])(*node), 1);
86 | }
87 | }();
88 | }
89 |
90 | pragma(inline, true)
91 | @nogc nothrow pure @trusted
92 | size_t opCmpPrefix(string op)(const Node *node, size_t val) const
93 | if ((op == "==") || (op == "<=") || (op == "<") || (op == ">=") || (op == ">"))
94 | {
95 | return _allocator.opCmpPrefix!op(cast(void[Node.sizeof])(*node), val);
96 | }
97 |
98 | static string immutableInsert(string stuff)
99 | {
100 | return q{
101 | _allocator = immutable AllocatorHandler(allocator);
102 | Node *tmpNode;
103 | Node *tmpHead;
104 | foreach (item; } ~ stuff ~ q{ )
105 | {
106 | Node *newNode;
107 | () @trusted { newNode =
108 | _allocator.make!(Node)(item, null);
109 | }();
110 | (tmpHead ? tmpNode._next : tmpHead) = newNode;
111 | tmpNode = newNode;
112 | }
113 | _head = (() @trusted => cast(immutable Node*)(tmpHead))();
114 | };
115 | }
116 |
117 | public:
118 | /**
119 | * Constructs a qualified singly linked list that will use the provided
120 | * allocator object. For `immutable` objects, a `RCISharedAllocator` must
121 | * be supplied.
122 | *
123 | * Params:
124 | * allocator = a $(REF RCIAllocator, std,experimental,allocator) or
125 | * $(REF RCISharedAllocator, std,experimental,allocator)
126 | * allocator object
127 | *
128 | * Complexity: $(BIGOH 1)
129 | */
130 | this(A, this Q)(A allocator)
131 | if (!is(Q == shared)
132 | && (is(A == RCISharedAllocator) || !is(Q == immutable))
133 | && (is(A == RCIAllocator) || is(A == RCISharedAllocator)))
134 | {
135 | debug(CollectionSList)
136 | {
137 | writefln("SList.ctor: begin");
138 | scope(exit) writefln("SList.ctor: end");
139 | }
140 | static if (is(Q == immutable) || is(Q == const))
141 | {
142 | T[] empty;
143 | this(allocator, empty);
144 | }
145 | else
146 | {
147 | setAllocator(allocator);
148 | }
149 | }
150 |
151 | ///
152 | static if (is(T == int))
153 | @safe unittest
154 | {
155 | auto sl = SList!int(theAllocator);
156 | auto csl = const SList!int(processAllocator);
157 | auto isl = immutable SList!int(processAllocator);
158 | }
159 |
160 | /**
161 | * Constructs a qualified singly linked list out of a number of items.
162 | * Because no allocator was provided, the list will use the
163 | * $(REF, GCAllocator, std,experimental,allocator).
164 | *
165 | * Params:
166 | * values = a variable number of items, either in the form of a
167 | * list or as a built-in array
168 | *
169 | * Complexity: $(BIGOH m), where `m` is the number of items.
170 | */
171 | this(U, this Q)(U[] values...)
172 | if (isImplicitlyConvertible!(U, T))
173 | {
174 | this(defaultAllocator!(typeof(this)), values);
175 | }
176 |
177 | ///
178 | static if (is(T == int))
179 | @safe unittest
180 | {
181 | import std.algorithm.comparison : equal;
182 |
183 | // Create a list from a list of ints
184 | {
185 | auto sl = SList!int(1, 2, 3);
186 | assert(equal(sl, [1, 2, 3]));
187 | }
188 | // Create a list from an array of ints
189 | {
190 | auto sl = SList!int([1, 2, 3]);
191 | assert(equal(sl, [1, 2, 3]));
192 | }
193 | // Create a list from a list from an input range
194 | {
195 | auto sl = SList!int(1, 2, 3);
196 | auto sl2 = SList!int(sl);
197 | assert(equal(sl2, [1, 2, 3]));
198 | }
199 | }
200 |
201 | /**
202 | * Constructs a qualified singly linked list out of a number of items
203 | * that will use the provided allocator object.
204 | * For `immutable` objects, a `RCISharedAllocator` must be supplied.
205 | *
206 | * Params:
207 | * allocator = a $(REF RCIAllocator, std,experimental,allocator) or
208 | * $(REF RCISharedAllocator, std,experimental,allocator)
209 | * allocator object
210 | * values = a variable number of items, either in the form of a
211 | * list or as a built-in array
212 | *
213 | * Complexity: $(BIGOH m), where `m` is the number of items.
214 | */
215 | this(A, U, this Q)(A allocator, U[] values...)
216 | if (!is(Q == shared)
217 | && (is(A == RCISharedAllocator) || !is(Q == immutable))
218 | && (is(A == RCIAllocator) || is(A == RCISharedAllocator))
219 | && isImplicitlyConvertible!(U, T))
220 | {
221 | debug(CollectionSList)
222 | {
223 | writefln("SList.ctor: begin");
224 | scope(exit) writefln("SList.ctor: end");
225 | }
226 | static if (is(Q == immutable) || is(Q == const))
227 | {
228 | mixin(immutableInsert("values"));
229 | }
230 | else
231 | {
232 | setAllocator(allocator);
233 | insert(0, values);
234 | }
235 | }
236 |
237 | /**
238 | * Constructs a qualified singly linked list out of an
239 | * $(REF_ALTTEXT input range, isInputRange, std,range,primitives).
240 | * Because no allocator was provided, the list will use the
241 | * $(REF, GCAllocator, std,experimental,allocator).
242 | *
243 | * Params:
244 | * stuff = an input range of elements that are implitictly convertible
245 | * to `T`
246 | *
247 | * Complexity: $(BIGOH m), where `m` is the number of elements in the range.
248 | */
249 | this(Stuff, this Q)(Stuff stuff)
250 | if (isInputRange!Stuff
251 | && isImplicitlyConvertible!(ElementType!Stuff, T)
252 | && !is(Stuff == T[]))
253 | {
254 | this(defaultAllocator!(typeof(this)), stuff);
255 | }
256 |
257 | /**
258 | * Constructs a qualified singly linked list out of an
259 | * $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
260 | * that will use the provided allocator object.
261 | * For `immutable` objects, a `RCISharedAllocator` must be supplied.
262 | *
263 | * Params:
264 | * allocator = a $(REF RCIAllocator, std,experimental,allocator) or
265 | * $(REF RCISharedAllocator, std,experimental,allocator)
266 | * allocator object
267 | * stuff = an input range of elements that are implitictly convertible
268 | * to `T`
269 | *
270 | * Complexity: $(BIGOH m), where `m` is the number of elements in the range.
271 | */
272 | this(A, Stuff, this Q)(A allocator, Stuff stuff)
273 | if (!is(Q == shared)
274 | && (is(A == RCISharedAllocator) || !is(Q == immutable))
275 | && (is(A == RCIAllocator) || is(A == RCISharedAllocator))
276 | && isInputRange!Stuff
277 | && isImplicitlyConvertible!(ElementType!Stuff, T)
278 | && !is(Stuff == T[]))
279 | {
280 | debug(CollectionSList)
281 | {
282 | writefln("SList.ctor: begin");
283 | scope(exit) writefln("SList.ctor: end");
284 | }
285 | static if (is(Q == immutable) || is(Q == const))
286 | {
287 | mixin(immutableInsert("stuff"));
288 | }
289 | else
290 | {
291 | setAllocator(allocator);
292 | insert(0, stuff);
293 | }
294 | }
295 |
296 | this(this)
297 | {
298 | debug(CollectionSList)
299 | {
300 | writefln("SList.postblit: begin");
301 | scope(exit) writefln("SList.postblit: end");
302 | }
303 | _allocator.bootstrap();
304 | if (_head !is null)
305 | {
306 | addRef(_head);
307 | debug(CollectionSList) writefln("SList.postblit: Node %s has refcount: %s",
308 | _head._payload, *prefCount(_head));
309 | }
310 | }
311 |
312 | // Immutable ctors
313 | // Very important to pass the allocator by ref! (Related to postblit bug)
314 | private this(NodeQual, AllocQual, this Q)(NodeQual _newHead, ref AllocQual _newAllocator)
315 | if (is(typeof(_head) : typeof(_newHead))
316 | && (is(Q == immutable) || is(Q == const)))
317 | {
318 | _head = _newHead;
319 | // Needs a bootstrap
320 | // bootstrap is the equivalent of incRef
321 | _newAllocator.bootstrap();
322 | _allocator = _newAllocator;
323 | if (_head !is null)
324 | {
325 | addRef(_head);
326 | debug(CollectionSList) writefln("SList.ctor immutable: Node %s has "
327 | ~ "refcount: %s", _head._payload, *sharedPrefCount(_head));
328 | }
329 | }
330 |
331 | ~this()
332 | {
333 | debug(CollectionSList)
334 | {
335 | writefln("SList.dtor: Begin for instance %s of type %s",
336 | cast(size_t)(&this), typeof(this).stringof);
337 | scope(exit) writefln("SList.dtor: End for instance %s of type %s",
338 | cast(size_t)(&this), typeof(this).stringof);
339 | }
340 | destroyUnused();
341 | }
342 |
343 | static if (is(T == int))
344 | nothrow pure @safe unittest
345 | {
346 | auto s = SList!int(1, 2, 3);
347 |
348 | // Infer safety
349 | static assert(!__traits(compiles, () @safe { SList!Unsafe(Unsafe(1)); }));
350 | static assert(!__traits(compiles, () @safe { auto s = const SList!Unsafe(Unsafe(1)); }));
351 | static assert(!__traits(compiles, () @safe { auto s = immutable SList!Unsafe(Unsafe(1)); }));
352 |
353 | static assert(!__traits(compiles, () @safe { SList!UnsafeDtor(UnsafeDtor(1)); }));
354 | static assert(!__traits(compiles, () @safe { auto s = const SList!UnsafeDtor(UnsafeDtor(1)); }));
355 | static assert(!__traits(compiles, () @safe { auto s = immutable SList!UnsafeDtor(UnsafeDtor(1)); }));
356 |
357 | // Infer purity
358 | static assert(!__traits(compiles, () pure { SList!Impure(Impure(1)); }));
359 | static assert(!__traits(compiles, () pure { auto s = const SList!Impure(Impure(1)); }));
360 | static assert(!__traits(compiles, () pure { auto s = immutable SList!Impure(Impure(1)); }));
361 |
362 | static assert(!__traits(compiles, () pure { SList!ImpureDtor(ImpureDtor(1)); }));
363 | static assert(!__traits(compiles, () pure { auto s = const SList!ImpureDtor(ImpureDtor(1)); }));
364 | static assert(!__traits(compiles, () pure { auto s = immutable SList!ImpureDtor(ImpureDtor(1)); }));
365 |
366 | // Infer throwability
367 | static assert(!__traits(compiles, () nothrow { SList!Throws(Throws(1)); }));
368 | static assert(!__traits(compiles, () nothrow { auto s = const SList!Throws(Throws(1)); }));
369 | static assert(!__traits(compiles, () nothrow { auto s = immutable SList!Throws(Throws(1)); }));
370 |
371 | static assert(!__traits(compiles, () nothrow { SList!ThrowsDtor(ThrowsDtor(1)); }));
372 | static assert(!__traits(compiles, () nothrow { auto s = const SList!ThrowsDtor(ThrowsDtor(1)); }));
373 | static assert(!__traits(compiles, () nothrow { auto s = immutable SList!ThrowsDtor(ThrowsDtor(1)); }));
374 | }
375 |
376 | private void destroyUnused()
377 | {
378 | debug(CollectionSList)
379 | {
380 | writefln("SList.destoryUnused: begin");
381 | scope(exit) writefln("SList.destoryUnused: end");
382 | }
383 | while (_head !is null && opCmpPrefix!"=="(_head, 0))
384 | {
385 | debug(CollectionSList) writefln("SList.destoryUnused: One ref with head at %s",
386 | _head._payload);
387 | Node *tmpNode = _head;
388 | _head = _head._next;
389 | delRef(tmpNode);
390 | }
391 |
392 | if (_head !is null && opCmpPrefix!">"(_head, 0))
393 | {
394 | // We reached a copy, so just remove the head ref, thus deleting
395 | // the copy in constant time (we are undoing the postblit)
396 | debug(CollectionSList) writefln("SList.destoryUnused: Multiple refs with head at %s",
397 | _head._payload);
398 | delRef(_head);
399 | }
400 | }
401 |
402 | /**
403 | * Check whether there are no more references to this list instance.
404 | *
405 | * Returns:
406 | * `true` if this is the only reference to this list instance;
407 | * `false` otherwise.
408 | *
409 | * Complexity: $(BIGOH n).
410 | */
411 | bool isUnique() const
412 | {
413 | debug(CollectionSList)
414 | {
415 | writefln("SList.isUnique: begin");
416 | scope(exit) writefln("SList.isUnique: end");
417 | }
418 |
419 | // TODO: change this to tail-impl for const/imm types
420 | Node *tmpNode = (() @trusted => cast(Node*)_head)();
421 | while (tmpNode !is null)
422 | {
423 | if (opCmpPrefix!">"(tmpNode, 0))
424 | {
425 | return false;
426 | }
427 | tmpNode = tmpNode._next;
428 | }
429 | return true;
430 | }
431 |
432 | ///
433 | static if (is(T == int))
434 | @safe unittest
435 | {
436 | auto sl = SList!int(24, 42);
437 | assert(sl.isUnique);
438 | {
439 | auto sl2 = sl;
440 | assert(!sl.isUnique);
441 | sl2.front = 0;
442 | assert(sl.front == 0);
443 | } // sl2 goes out of scope
444 | assert(sl.isUnique);
445 | }
446 |
447 | /**
448 | * Check if the list is empty.
449 | *
450 | * Returns:
451 | * `true` if there are no nodes in the list; `false` otherwise.
452 | *
453 | * Complexity: $(BIGOH 1).
454 | */
455 | @nogc nothrow pure @safe
456 | bool empty() const
457 | {
458 | return _head is null;
459 | }
460 |
461 | ///
462 | static if (is(T == int))
463 | @safe unittest
464 | {
465 | SList!int sl;
466 | assert(sl.empty);
467 | size_t pos = 0;
468 | sl.insert(pos, 1);
469 | assert(!sl.empty);
470 | }
471 |
472 | /**
473 | * Provide access to the first element in the list. The user must check
474 | * that the list isn't `empty`, prior to calling this function.
475 | *
476 | * Returns:
477 | * a reference to the first element.
478 | *
479 | * Complexity: $(BIGOH 1).
480 | */
481 | ref auto front(this _)()
482 | {
483 | assert(!empty, "SList.front: List is empty");
484 | return _head._payload;
485 | }
486 |
487 | ///
488 | static if (is(T == int))
489 | @safe unittest
490 | {
491 | auto sl = SList!int(1, 2, 3);
492 | assert(sl.front == 1);
493 | sl.front = 0;
494 | assert(sl.front == 0);
495 | }
496 |
497 | /**
498 | * Advance to the next element in the list. The user must check
499 | * that the list isn't `empty`, prior to calling this function.
500 | *
501 | * If there are no more references to the current element (which is being
502 | * consumed), then the current element will be destroyed; this will call
503 | * `T`'s dtor, if one is defined, and will collect it's resources.
504 | *
505 | * Complexity: $(BIGOH 1).
506 | */
507 | void popFront()
508 | {
509 | debug(CollectionSList)
510 | {
511 | writefln("SList.popFront: begin");
512 | scope(exit) writefln("SList.popFront: end");
513 | }
514 | assert(!empty, "SList.popFront: List is empty");
515 |
516 | Node *tmpNode = _head;
517 | _head = _head._next;
518 | if (opCmpPrefix!">"(tmpNode, 0) && _head !is null)
519 | {
520 | // If we have another copy of the list then the refcount
521 | // must increase, otherwise it will remain the same
522 | // This condition is needed because the recounting is zero based
523 | addRef(_head);
524 | }
525 | delRef(tmpNode);
526 | }
527 |
528 | ///
529 | static if (is(T == int))
530 | @safe unittest
531 | {
532 | auto a = [1, 2, 3];
533 | auto sl = SList!int(a);
534 | size_t i = 0;
535 | while (!sl.empty)
536 | {
537 | assert(sl.front == a[i++]);
538 | sl.popFront;
539 | }
540 | assert(sl.empty);
541 | }
542 |
543 | /**
544 | * Advance to the next element in the list. The user must check
545 | * that the list isn't `empty`, prior to calling this function.
546 | *
547 | * This must be used in order to iterate through a `const` or `immutable`
548 | * list. For a mutable list this is equivalent to calling `popFront`.
549 | *
550 | * Returns:
551 | * a list that starts with the next element in the original list
552 | *
553 | * Complexity: $(BIGOH 1).
554 | */
555 | Qualified tail(this Qualified)()
556 | {
557 | debug(CollectionSList)
558 | {
559 | writefln("SList.tail: begin");
560 | scope(exit) writefln("SList.tail: end");
561 | }
562 | assert(!empty, "SList.tail: List is empty");
563 |
564 | static if (is(Qualified == immutable) || is(Qualified == const))
565 | {
566 | return Qualified(_head._next, _allocator);
567 | }
568 | else
569 | {
570 | return .tail(this);
571 | }
572 | }
573 |
574 | ///
575 | static if (is(T == int))
576 | @safe unittest
577 | {
578 | auto isl = immutable SList!int([1, 2, 3]);
579 | assert(isl.tail.front == 2);
580 | }
581 |
582 | /**
583 | * Eagerly iterate over each element in the list and call `fun` over each
584 | * element. This should be used to iterate through `const` and `immutable`
585 | * lists.
586 | *
587 | * Normally, the entire list is iterated. If partial iteration (early stopping)
588 | * is desired, `fun` needs to return a value of type
589 | * $(REF Flag, std,typecons)`!"each"` (`Yes.each` to continue iteration, or
590 | * `No.each` to stop).
591 | *
592 | * Params:
593 | * fun = unary function to apply on each element of the list.
594 | *
595 | * Returns:
596 | * `Yes.each` if it has iterated through all the elements in the list,
597 | * or `No.each` otherwise.
598 | *
599 | * Complexity: $(BIGOH n).
600 | */
601 | template each(alias fun)
602 | {
603 | import std.typecons : Flag, Yes, No;
604 | import std.functional : unaryFun;
605 |
606 | Flag!"each" each(this Q)()
607 | if (is (typeof(unaryFun!fun(T.init))))
608 | {
609 | alias fn = unaryFun!fun;
610 |
611 | auto sl = SList!(const SList!T)(this);
612 | while (!sl.empty && !sl.front.empty)
613 | {
614 | static if (!is(typeof(fn(T.init)) == Flag!"each"))
615 | {
616 | cast(void) fn(sl.front.front);
617 | }
618 | else
619 | {
620 | if (fn(sl.front.front) == No.each)
621 | return No.each;
622 | }
623 | sl ~= sl.front.tail;
624 | sl.popFront;
625 | }
626 | return Yes.each;
627 | }
628 | }
629 |
630 | ///
631 | static if (is(T == int))
632 | @safe unittest
633 | {
634 | import std.typecons : Flag, Yes, No;
635 |
636 | auto isl = immutable SList!int([1, 2, 3]);
637 |
638 | static bool foo(int x) { return x > 0; }
639 |
640 | assert(isl.each!foo == Yes.each);
641 | }
642 |
643 | /**
644 | * Perform a shallow copy of the list.
645 | *
646 | * Returns:
647 | * a new reference to the current list.
648 | *
649 | * Complexity: $(BIGOH 1).
650 | */
651 | ref Qualified save(this Qualified)()
652 | {
653 | debug(CollectionSList)
654 | {
655 | writefln("SList.save: begin");
656 | scope(exit) writefln("SList.save: end");
657 | }
658 | return this;
659 | }
660 |
661 | ///
662 | static if (is(T == int))
663 | @safe unittest
664 | {
665 | auto a = [1, 2, 3];
666 | auto sl = SList!int(a);
667 | size_t i = 0;
668 |
669 | auto tmp = sl.save;
670 | while (!tmp.empty)
671 | {
672 | assert(tmp.front == a[i++]);
673 | tmp.popFront;
674 | }
675 | assert(tmp.empty);
676 | assert(!sl.empty);
677 | }
678 |
679 | /**
680 | * Perform a copy of the list. This will create a new list that will copy
681 | * the elements of the current list. This will `NOT` call `dup` on the
682 | * elements of the list, regardless if `T` defines it or not.
683 | *
684 | * Returns:
685 | * a new list.
686 | *
687 | * Complexity: $(BIGOH n).
688 | */
689 | SList!T dup(this Q)()
690 | {
691 | debug(CollectionSList)
692 | {
693 | writefln("SList.dup: begin");
694 | scope(exit) writefln("SList.dup: end");
695 | }
696 |
697 | SList!T result;
698 | result._allocator = _allocator;
699 |
700 | static if (is(Q == immutable) || is(Q == const))
701 | {
702 | static void appendEach(ref SList!T sl, const SList!T isl)
703 | {
704 | if (isl.empty) return;
705 | sl ~= isl.front;
706 | appendEach(sl, isl.tail);
707 | }
708 |
709 | appendEach(result, this);
710 | }
711 | else
712 | {
713 | result.insert(0, this);
714 | }
715 | return result;
716 | }
717 |
718 | ///
719 | static if (is(T == int))
720 | @safe unittest
721 | {
722 | import std.algorithm.comparison : equal;
723 |
724 | auto stuff = [1, 2, 3];
725 | auto sl = immutable SList!int(stuff);
726 | auto slDup = sl.dup;
727 | assert(equal(slDup, stuff));
728 | slDup.front = 0;
729 | assert(slDup.front == 0);
730 | assert(sl.front == 1);
731 | }
732 |
733 | /**
734 | * Inserts the elements of an
735 | * $(REF_ALTTEXT input range, isInputRange, std,range,primitives), or a
736 | * variable number of items, at the given `pos`.
737 | *
738 | * If no allocator was provided when the list was created, the
739 | * $(REF, GCAllocator, std,experimental,allocator) will be used.
740 | *
741 | * Params:
742 | * pos = a positive integer
743 | * stuff = an input range of elements that are implitictly convertible
744 | * to `T`; a variable number of items either in the form of a
745 | * list or as a built-in array
746 | *
747 | * Returns:
748 | * the number of elements inserted
749 | *
750 | * Complexity: $(BIGOH pos + m), where `m` is the number of elements in the range.
751 | */
752 | size_t insert(Stuff)(size_t pos, Stuff stuff)
753 | if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
754 | {
755 | debug(CollectionSList)
756 | {
757 | writefln("SList.insert: begin");
758 | scope(exit) writefln("SList.insert: end");
759 | }
760 |
761 | // Will be optimized away, but the type system infers T's safety
762 | if (0) { T t = T.init; }
763 |
764 | // Ensure we have an allocator. If it was already set, this will do nothing
765 | auto a = threadAllocatorObject();
766 | setAllocator(a);
767 |
768 | size_t result;
769 | Node *tmpNode;
770 | Node *tmpHead;
771 | foreach (item; stuff)
772 | {
773 | Node *newNode;
774 | () @trusted { newNode = _allocator.make!(Node)(item, null); }();
775 | (tmpHead ? tmpNode._next : tmpHead) = newNode;
776 | tmpNode = newNode;
777 | ++result;
778 | }
779 |
780 | if (!tmpNode)
781 | {
782 | return 0;
783 | }
784 |
785 | Node *needle = _head;
786 | Node *needlePrev = null;
787 | while (pos)
788 | {
789 | needlePrev = needle;
790 | needle = needle._next;
791 | --pos;
792 | }
793 |
794 | tmpNode._next = needle;
795 | if (needlePrev is null)
796 | {
797 | _head = tmpHead;
798 | }
799 | else
800 | {
801 | needlePrev._next = tmpHead;
802 | }
803 | return result;
804 | }
805 |
806 | /// ditto
807 | size_t insert(Stuff)(size_t pos, Stuff[] stuff...)
808 | if (isImplicitlyConvertible!(Stuff, T))
809 | {
810 | return insert(pos, stuff);
811 | }
812 |
813 | ///
814 | static if (is(T == int))
815 | @safe unittest
816 | {
817 | import std.algorithm.comparison : equal;
818 |
819 | auto s = SList!int(4, 5);
820 | SList!int sl;
821 | assert(sl.empty);
822 |
823 | size_t pos = 0;
824 | pos += sl.insert(pos, 1);
825 | pos += sl.insert(pos, [2, 3]);
826 | assert(equal(sl, [1, 2, 3]));
827 |
828 | // insert from an input range
829 | pos += sl.insert(pos, s);
830 | assert(equal(sl, [1, 2, 3, 4, 5]));
831 | s.front = 0;
832 | assert(equal(sl, [1, 2, 3, 4, 5]));
833 | }
834 |
835 | /**
836 | * Inserts the elements of an
837 | * $(REF_ALTTEXT input range, isInputRange, std,range,primitives), or a
838 | * variable number of items, at the end of the list.
839 | *
840 | * If no allocator was provided when the list was created, the
841 | * $(REF, GCAllocator, std,experimental,allocator) will be used.
842 | *
843 | * Params:
844 | * stuff = an input range of elements that are implitictly convertible
845 | * to `T`; a variable number of items either in the form of a
846 | * list or as a built-in array
847 | *
848 | * Returns:
849 | * the number of elements inserted
850 | *
851 | * Complexity: $(BIGOH pos + m), where `m` is the number of elements in the range.
852 | */
853 | size_t insertBack(Stuff)(Stuff stuff)
854 | if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
855 | {
856 | debug(CollectionSList)
857 | {
858 | writefln("SList.insertBack: begin");
859 | scope(exit) writefln("SList.insertBack: end");
860 | }
861 |
862 | // Will be optimized away, but the type system infers T's safety
863 | if (0) { T t = T.init; }
864 |
865 | // Ensure we have an allocator. If it was already set, this will do nothing
866 | auto a = threadAllocatorObject();
867 | setAllocator(a);
868 |
869 | size_t result;
870 | Node *tmpNode;
871 | Node *tmpHead;
872 | foreach (item; stuff)
873 | {
874 | Node *newNode;
875 | () @trusted { newNode = _allocator.make!(Node)(item, null); }();
876 | (tmpHead ? tmpNode._next : tmpHead) = newNode;
877 | tmpNode = newNode;
878 | ++result;
879 | }
880 |
881 | if (!tmpNode)
882 | {
883 | return 0;
884 | }
885 |
886 | if (_head is null)
887 | {
888 | _head = tmpHead;
889 | }
890 | else
891 | {
892 | Node *endNode;
893 | for (endNode = _head; endNode._next !is null; endNode = endNode._next) { }
894 | endNode._next = tmpHead;
895 | }
896 |
897 | return result;
898 | }
899 |
900 | /// ditto
901 | size_t insertBack(Stuff)(Stuff[] stuff...)
902 | if (isImplicitlyConvertible!(Stuff, T))
903 | {
904 | return insertBack(stuff);
905 | }
906 |
907 | ///
908 | static if (is(T == int))
909 | @safe unittest
910 | {
911 | import std.algorithm.comparison : equal;
912 |
913 | auto s = SList!int(4, 5);
914 | SList!int sl;
915 | assert(sl.empty);
916 |
917 | sl.insertBack(1);
918 | sl.insertBack([2, 3]);
919 | assert(equal(sl, [1, 2, 3]));
920 |
921 | // insert from an input range
922 | sl.insertBack(s);
923 | assert(equal(sl, [1, 2, 3, 4, 5]));
924 | s.front = 0;
925 | assert(equal(sl, [1, 2, 3, 4, 5]));
926 | }
927 |
928 | /**
929 | * Create a new list that results from the concatenation of this list
930 | * with `rhs`.
931 | *
932 | * Params:
933 | * rhs = can be an element that is implicitly convertible to `T`, an
934 | * input range of such elements, or another singly linked list
935 | *
936 | * Returns:
937 | * the newly created list
938 | *
939 | * Complexity: $(BIGOH n + m), where `m` is the number of elements in `rhs`.
940 | */
941 | auto ref opBinary(string op, U)(auto ref U rhs)
942 | if (op == "~" &&
943 | //(is (U : const typeof(this))
944 | (is (U == typeof(this))
945 | || is (U : T)
946 | || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
947 | ))
948 | {
949 | debug(CollectionSList)
950 | {
951 | writefln("SList.opBinary!~: begin");
952 | scope(exit) writefln("SList.opBinary!~: end");
953 | }
954 |
955 | auto newList = this.dup();
956 | newList.insertBack(rhs);
957 | return newList;
958 | }
959 |
960 | ///
961 | static if (is(T == int))
962 | @safe unittest
963 | {
964 | import std.algorithm.comparison : equal;
965 |
966 | auto sl = SList!int(1);
967 | auto sl2 = sl ~ 2;
968 |
969 | assert(equal(sl2, [1, 2]));
970 | sl.front = 0;
971 | assert(equal(sl2, [1, 2]));
972 | }
973 |
974 | /**
975 | * Assign `rhs` to this list. The current list will now become another
976 | * reference to `rhs`, unless `rhs` is `null`, in which case the current
977 | * list will become empty. If `rhs` refers to the current list nothing will
978 | * happen.
979 | *
980 | * All the previous list elements that have no more references to them
981 | * will be destroyed; this leads to a $(BIGOH n) complexity.
982 | *
983 | * Params:
984 | * rhs = a reference to a singly linked list
985 | *
986 | * Returns:
987 | * a reference to this list
988 | *
989 | * Complexity: $(BIGOH n).
990 | */
991 | auto ref opAssign()(auto ref typeof(this) rhs)
992 | {
993 | debug(CollectionSList)
994 | {
995 | writefln("SList.opAssign: begin");
996 | scope(exit) writefln("SList.opAssign: end");
997 | }
998 |
999 | if (rhs._head !is null && _head is rhs._head)
1000 | {
1001 | return this;
1002 | }
1003 |
1004 | if (rhs._head !is null)
1005 | {
1006 | rhs.addRef(rhs._head);
1007 | debug(CollectionSList) writefln("SList.opAssign: Node %s has refcount: %s",
1008 | rhs._head._payload, *prefCount(rhs._head));
1009 | }
1010 | destroyUnused();
1011 | _head = rhs._head;
1012 | _allocator = rhs._allocator;
1013 | return this;
1014 | }
1015 |
1016 | ///
1017 | static if (is(T == int))
1018 | @safe unittest
1019 | {
1020 | import std.algorithm.comparison : equal;
1021 |
1022 | auto sl = SList!int(1);
1023 | auto sl2 = SList!int(1, 2);
1024 |
1025 | sl = sl2; // this will free the old sl
1026 | assert(equal(sl, [1, 2]));
1027 | sl.front = 0;
1028 | assert(equal(sl2, [0, 2]));
1029 | }
1030 |
1031 | /**
1032 | * Append the elements of `rhs` at the end of the list.
1033 | *
1034 | * If no allocator was provided when the list was created, the
1035 | * $(REF, GCAllocator, std,experimental,allocator) will be used.
1036 | *
1037 | * Params:
1038 | * rhs = can be an element that is implicitly convertible to `T`, an
1039 | * input range of such elements, or another singly linked list
1040 | *
1041 | * Returns:
1042 | * a reference to this list
1043 | *
1044 | * Complexity: $(BIGOH n + m), where `m` is the number of elements in `rhs`.
1045 | */
1046 | auto ref opOpAssign(string op, U)(auto ref U rhs)
1047 | if (op == "~" &&
1048 | (is (U == typeof(this))
1049 | || is (U : T)
1050 | || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
1051 | ))
1052 | {
1053 | debug(CollectionSList)
1054 | {
1055 | writefln("SList.opOpAssign!~: %s begin", typeof(this).stringof);
1056 | scope(exit) writefln("SList.opOpAssign!~: %s end", typeof(this).stringof);
1057 | }
1058 |
1059 | insertBack(rhs);
1060 | return this;
1061 | }
1062 |
1063 | ///
1064 | static if (is(T == int))
1065 | @safe unittest
1066 | {
1067 | import std.algorithm.comparison : equal;
1068 |
1069 | auto s = SList!int(4, 5);
1070 | SList!int sl;
1071 | assert(sl.empty);
1072 |
1073 | sl ~= 1;
1074 | sl ~= [2, 3];
1075 | assert(equal(sl, [1, 2, 3]));
1076 |
1077 | // append an input range
1078 | sl ~= s;
1079 | assert(equal(sl, [1, 2, 3, 4, 5]));
1080 | s.front = 0;
1081 | assert(equal(sl, [1, 2, 3, 4, 5]));
1082 | }
1083 |
1084 | /**
1085 | * Remove the element at the given `idx` from the list. If there are no
1086 | * more references to the given element, then it will be destroyed.
1087 | *
1088 | * Params:
1089 | * idx = a positive integer
1090 | */
1091 | void remove(size_t idx = 0)
1092 | {
1093 | assert(!empty, "SList.remove: List is empty");
1094 | if (idx == 0)
1095 | {
1096 | popFront();
1097 | return;
1098 | }
1099 |
1100 | Node *tmpNode = _head;
1101 | while(--idx != 0)
1102 | {
1103 | tmpNode = tmpNode._next;
1104 | }
1105 | Node *toDel = tmpNode._next;
1106 | assert(toDel !is null, "SList.remove: Index out of bounds");
1107 | tmpNode._next = tmpNode._next._next;
1108 | delRef(toDel);
1109 | }
1110 |
1111 | ///
1112 | static if (is(T == int))
1113 | @safe unittest
1114 | {
1115 | import std.algorithm.comparison : equal;
1116 |
1117 | auto sl = SList!int(1, 2, 3);
1118 | auto sl2 = sl;
1119 | auto pos = 1;
1120 |
1121 | assert(equal(sl, [1, 2, 3]));
1122 | sl.remove(pos);
1123 | assert(equal(sl, [1, 3]));
1124 | assert(equal(sl2, [1, 3]));
1125 | }
1126 |
1127 | debug(CollectionSList)
1128 | void printRefCount() const
1129 | {
1130 | writefln("SList.printRefCount: begin");
1131 | scope(exit) writefln("SList.printRefCount: end");
1132 |
1133 | Node *tmpNode = (() @trusted => cast(Node*)_head)();
1134 | while (tmpNode !is null)
1135 | {
1136 | writefln("SList.printRefCount: Node %s has ref count %s",
1137 | tmpNode._payload, *localPrefCount(tmpNode));
1138 | tmpNode = tmpNode._next;
1139 | }
1140 | }
1141 | }
1142 |
1143 | version(unittest) private nothrow pure @safe
1144 | void testImmutability(RCISharedAllocator allocator)
1145 | {
1146 | auto s = immutable SList!(int)(allocator, 1, 2, 3);
1147 | auto s2 = s;
1148 | auto s3 = s2.save();
1149 |
1150 | assert(s2.front == 1);
1151 | static assert(!__traits(compiles, s2.front = 4));
1152 | static assert(!__traits(compiles, s2.popFront()));
1153 |
1154 | auto s4 = s2.tail;
1155 | assert(s4.front == 2);
1156 | static assert(!__traits(compiles, s4 = s4.tail));
1157 | }
1158 |
1159 | version(unittest) private nothrow pure @safe
1160 | void testConstness(RCISharedAllocator allocator)
1161 | {
1162 | auto s = const SList!(int)(allocator, 1, 2, 3);
1163 | auto s2 = s;
1164 | auto s3 = s2.save();
1165 |
1166 | assert(s2.front == 1);
1167 | static assert(!__traits(compiles, s2.front = 4));
1168 | static assert(!__traits(compiles, s2.popFront()));
1169 |
1170 | auto s4 = s2.tail;
1171 | assert(s4.front == 2);
1172 | static assert(!__traits(compiles, s4 = s4.tail));
1173 | }
1174 |
1175 | @safe unittest
1176 | {
1177 | import std.conv;
1178 | import std.experimental.allocator : processAllocator;
1179 | SCAlloc statsCollectorAlloc;
1180 | {
1181 | // TODO: StatsCollector need to be made shareable
1182 | //auto _allocator = sharedAllocatorObject(&statsCollectorAlloc);
1183 | () nothrow pure @safe {
1184 | testImmutability(processAllocatorObject());
1185 | testConstness(processAllocatorObject());
1186 | }();
1187 | }
1188 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1189 | assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
1190 | ~ to!string(bytesUsed) ~ " bytes");
1191 | }
1192 |
1193 | version(unittest) private nothrow pure @safe
1194 | void testConcatAndAppend(RCIAllocator allocator)
1195 | {
1196 | import std.algorithm.comparison : equal;
1197 |
1198 | auto sl = SList!(int)(allocator, 1, 2, 3);
1199 | SList!(int) sl2 = SList!int(allocator);
1200 |
1201 | auto sl3 = sl ~ sl2;
1202 | assert(equal(sl3, [1, 2, 3]));
1203 |
1204 | auto sl4 = sl3;
1205 | sl3 = sl3 ~ 4;
1206 | assert(equal(sl3, [1, 2, 3, 4]));
1207 | sl3 = sl3 ~ [5];
1208 | assert(equal(sl3, [1, 2, 3, 4, 5]));
1209 | assert(equal(sl4, [1, 2, 3]));
1210 |
1211 | sl4 = sl3;
1212 | sl3 ~= 6;
1213 | assert(equal(sl3, [1, 2, 3, 4, 5, 6]));
1214 | sl3 ~= [7];
1215 | assert(equal(sl3, [1, 2, 3, 4, 5, 6, 7]));
1216 | assert(equal(sl4, [1, 2, 3, 4, 5, 6, 7]));
1217 |
1218 | sl3 ~= sl3;
1219 | assert(equal(sl3, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]));
1220 | assert(equal(sl4, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]));
1221 |
1222 | SList!int sl5 = SList!int(allocator);
1223 | sl5 ~= [1, 2, 3];
1224 | assert(equal(sl5, [1, 2, 3]));
1225 | }
1226 |
1227 | @safe unittest
1228 | {
1229 | import std.conv;
1230 | SCAlloc statsCollectorAlloc;
1231 | {
1232 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1233 | () nothrow pure @safe {
1234 | testConcatAndAppend(_allocator);
1235 | }();
1236 | }
1237 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1238 | assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
1239 | ~ to!string(bytesUsed) ~ " bytes");
1240 | }
1241 |
1242 | version(unittest) private nothrow pure @safe
1243 | void testSimple(RCIAllocator allocator)
1244 | {
1245 | import std.algorithm.comparison : equal;
1246 | import std.algorithm.searching : canFind;
1247 | import std.range.primitives : walkLength;
1248 |
1249 | auto sl = SList!int(allocator);
1250 | assert(sl.empty);
1251 | assert(sl.isUnique);
1252 |
1253 | size_t pos = 0;
1254 | sl.insert(pos, 1, 2, 3);
1255 | assert(sl.front == 1);
1256 | assert(equal(sl, sl));
1257 | assert(equal(sl, [1, 2, 3]));
1258 |
1259 | sl.popFront();
1260 | assert(sl.front == 2);
1261 | assert(equal(sl, [2, 3]));
1262 |
1263 | sl.insert(pos, [4, 5, 6]);
1264 | sl.insert(pos, 7);
1265 | sl.insert(pos, [8]);
1266 | assert(equal(sl, [8, 7, 4, 5, 6, 2, 3]));
1267 |
1268 | sl.insertBack(0, 1);
1269 | sl.insertBack([-1, -2]);
1270 | assert(equal(sl, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
1271 |
1272 | sl.front = 9;
1273 | assert(equal(sl, [9, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
1274 |
1275 | auto slTail = sl.tail;
1276 | assert(slTail.front == 7);
1277 | slTail.front = 8;
1278 | assert(slTail.front == 8);
1279 | assert(sl.tail.front == 8);
1280 | assert(!sl.isUnique);
1281 |
1282 | assert(canFind(sl, 2));
1283 | assert(!canFind(sl, -10));
1284 |
1285 | sl.remove();
1286 | assert(equal(sl, [8, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
1287 | sl.remove(2);
1288 | assert(equal(sl, [8, 4, 6, 2, 3, 0, 1, -1, -2]));
1289 | sl.remove(walkLength(sl) - 1);
1290 | assert(equal(sl, [8, 4, 6, 2, 3, 0, 1, -1]));
1291 | pos = 1;
1292 | sl.insert(pos, 10);
1293 | assert(equal(sl, [8, 10, 4, 6, 2, 3, 0, 1, -1]));
1294 | }
1295 |
1296 | @safe unittest
1297 | {
1298 | import std.conv;
1299 | SCAlloc statsCollectorAlloc;
1300 | {
1301 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1302 | () nothrow pure @safe {
1303 | testSimple(_allocator);
1304 | }();
1305 | }
1306 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1307 | assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
1308 | ~ to!string(bytesUsed) ~ " bytes");
1309 | }
1310 |
1311 | version(unittest) private nothrow pure @safe
1312 | void testSimpleImmutable(RCIAllocator allocator)
1313 | {
1314 | import std.algorithm.comparison : equal;
1315 | import std.algorithm.searching : canFind;
1316 | import std.range.primitives : walkLength;
1317 |
1318 | auto sl = SList!(immutable int)(allocator);
1319 | assert(sl.empty);
1320 |
1321 | size_t pos = 0;
1322 | sl.insert(pos, 1, 2, 3);
1323 | assert(sl.front == 1);
1324 | assert(equal(sl, sl));
1325 | assert(equal(sl, [1, 2, 3]));
1326 |
1327 | sl.popFront();
1328 | assert(sl.front == 2);
1329 | assert(equal(sl, [2, 3]));
1330 | assert(sl.tail.front == 3);
1331 |
1332 | sl.insert(pos, [4, 5, 6]);
1333 | sl.insert(pos, 7);
1334 | sl.insert(pos, [8]);
1335 | assert(equal(sl, [8, 7, 4, 5, 6, 2, 3]));
1336 |
1337 | sl.insertBack(0, 1);
1338 | sl.insertBack([-1, -2]);
1339 | assert(equal(sl, [8, 7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
1340 |
1341 | // Cannot modify immutable values
1342 | static assert(!__traits(compiles, sl.front = 9));
1343 |
1344 | assert(canFind(sl, 2));
1345 | assert(!canFind(sl, -10));
1346 |
1347 | sl.remove();
1348 | assert(equal(sl, [7, 4, 5, 6, 2, 3, 0, 1, -1, -2]));
1349 | sl.remove(2);
1350 | assert(equal(sl, [7, 4, 6, 2, 3, 0, 1, -1, -2]));
1351 | sl.remove(walkLength(sl) - 1);
1352 | assert(equal(sl, [7, 4, 6, 2, 3, 0, 1, -1]));
1353 | }
1354 |
1355 | @safe unittest
1356 | {
1357 | import std.conv;
1358 | SCAlloc statsCollectorAlloc;
1359 | {
1360 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1361 | () nothrow pure @safe {
1362 | testSimpleImmutable(_allocator);
1363 | }();
1364 | }
1365 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1366 | assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
1367 | ~ to!string(bytesUsed) ~ " bytes");
1368 | }
1369 |
1370 | version(unittest) private nothrow pure @safe
1371 | void testCopyAndRef(RCIAllocator allocator)
1372 | {
1373 | import std.algorithm.comparison : equal;
1374 |
1375 | auto slFromList = SList!int(allocator, 1, 2, 3);
1376 | auto slFromRange = SList!int(allocator, slFromList);
1377 | assert(equal(slFromList, slFromRange));
1378 |
1379 | slFromList.popFront();
1380 | assert(equal(slFromList, [2, 3]));
1381 | assert(equal(slFromRange, [1, 2, 3]));
1382 |
1383 | SList!int slInsFromRange = SList!int(allocator);
1384 | size_t pos = 0;
1385 | slInsFromRange.insert(pos, slFromList);
1386 | slFromList.popFront();
1387 | assert(equal(slFromList, [3]));
1388 | assert(equal(slInsFromRange, [2, 3]));
1389 |
1390 | SList!int slInsBackFromRange = SList!int(allocator);
1391 | slInsBackFromRange.insert(pos, slFromList);
1392 | slFromList.popFront();
1393 | assert(slFromList.empty);
1394 | assert(equal(slInsBackFromRange, [3]));
1395 |
1396 | auto slFromRef = slInsFromRange;
1397 | auto slFromDup = slInsFromRange.dup;
1398 | assert(slInsFromRange.front == 2);
1399 | slFromRef.front = 5;
1400 | assert(slInsFromRange.front == 5);
1401 | assert(slFromDup.front == 2);
1402 | }
1403 |
1404 | @safe unittest
1405 | {
1406 | import std.conv;
1407 | SCAlloc statsCollectorAlloc;
1408 | {
1409 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1410 | () nothrow pure @safe {
1411 | testCopyAndRef(_allocator);
1412 | }();
1413 | }
1414 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1415 | assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
1416 | ~ to!string(bytesUsed) ~ " bytes");
1417 | }
1418 |
1419 | version(unittest) private nothrow pure @safe
1420 | void testWithStruct(RCIAllocator allocator, RCISharedAllocator sharedAlloc)
1421 | {
1422 | import std.algorithm.comparison : equal;
1423 |
1424 | auto list = SList!int(allocator, 1, 2, 3);
1425 | {
1426 | auto listOfLists = SList!(SList!int)(allocator, list);
1427 | assert(equal(listOfLists.front, [1, 2, 3]));
1428 | listOfLists.front.front = 2;
1429 | assert(equal(listOfLists.front, [2, 2, 3]));
1430 | size_t pos = 0;
1431 | static assert(!__traits(compiles, listOfLists.insert(pos, 1)));
1432 |
1433 | auto immListOfLists = immutable SList!(SList!int)(sharedAlloc, list);
1434 | assert(immListOfLists.front.front == 2);
1435 | static assert(!__traits(compiles, immListOfLists.front.front = 2));
1436 | }
1437 | assert(equal(list, [2, 2, 3]));
1438 | }
1439 |
1440 | @safe unittest
1441 | {
1442 | import std.conv;
1443 | import std.experimental.allocator : processAllocator;
1444 | SCAlloc statsCollectorAlloc;
1445 | {
1446 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1447 | () nothrow pure @safe {
1448 | testWithStruct(_allocator, processAllocatorObject());
1449 | }();
1450 | }
1451 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1452 | assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
1453 | ~ to!string(bytesUsed) ~ " bytes");
1454 | }
1455 |
1456 | version(unittest) private nothrow pure @safe
1457 | void testWithClass(RCIAllocator allocator)
1458 | {
1459 | class MyClass
1460 | {
1461 | int x;
1462 | this(int x) { this.x = x; }
1463 | }
1464 |
1465 | MyClass c = new MyClass(10);
1466 | {
1467 | auto sl = SList!MyClass(allocator, c);
1468 | assert(sl.front.x == 10);
1469 | assert(sl.front is c);
1470 | sl.front.x = 20;
1471 | }
1472 | assert(c.x == 20);
1473 | }
1474 |
1475 | @safe unittest
1476 | {
1477 | import std.conv;
1478 | SCAlloc statsCollectorAlloc;
1479 | {
1480 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1481 | () nothrow pure @safe {
1482 | testWithClass(_allocator);
1483 | }();
1484 | }
1485 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1486 | assert(bytesUsed == 0, "SList ref count leaks memory; leaked "
1487 | ~ to!string(bytesUsed) ~ " bytes");
1488 | }
1489 |
--------------------------------------------------------------------------------
/source/stdx/collections/dlist.d:
--------------------------------------------------------------------------------
1 | ///
2 | module stdx.collections.dlist;
3 |
4 | import stdx.collections.common;
5 |
6 | debug(CollectionDList) import std.stdio;
7 |
8 | version(unittest)
9 | {
10 | import std.experimental.allocator.mallocator;
11 | import std.experimental.allocator.building_blocks.stats_collector;
12 | import std.experimental.allocator : RCIAllocator, RCISharedAllocator,
13 | allocatorObject, sharedAllocatorObject;
14 |
15 | private alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed);
16 | }
17 |
18 | ///
19 | struct DList(T)
20 | {
21 | import std.experimental.allocator : RCIAllocator, RCISharedAllocator,
22 | theAllocator, processAllocator, make, dispose, stateSize;
23 | import std.experimental.allocator.building_blocks.affix_allocator;
24 | import std.traits : isImplicitlyConvertible;
25 | import std.range.primitives : isInputRange, ElementType;
26 | import std.variant : Algebraic;
27 | import std.conv : emplace;
28 | import core.atomic : atomicOp;
29 | import std.algorithm.mutation : move;
30 |
31 | private:
32 | struct Node
33 | {
34 | T _payload;
35 | Node *_next;
36 | Node *_prev;
37 |
38 | this(T v, Node *n, Node *p)
39 | {
40 | debug(CollectionDList) writefln("DList.Node.ctor: Constructing node" ~
41 | " with payload: %s", v);
42 | _payload = v;
43 | _next = n;
44 | _prev = p;
45 | }
46 |
47 | ~this()
48 | {
49 | debug(CollectionDList) writefln("DList.Node.dtor: Destroying node" ~
50 | " with payload: %s", _payload);
51 | }
52 | }
53 |
54 | // State {
55 | Node *_head;
56 | mixin(allocatorHandler);
57 | // }
58 |
59 | @nogc nothrow pure @trusted
60 | void addRef(QualNode, this Q)(QualNode node)
61 | {
62 | assert(node !is null);
63 | cast(void) _allocator.opPrefix!("+=")(cast(void[Node.sizeof])(*node), 1);
64 | }
65 |
66 | void delRef(ref Node *node)
67 | {
68 | // Will be optimized away, but the type system infers T's safety
69 | if (0) { T t = T.init; }
70 |
71 | assert(node !is null);
72 | () @trusted {
73 | if (opCmpPrefix!"=="(node, 0))
74 | {
75 | dispose(_allocator, node);
76 | node = null;
77 | }
78 | else
79 | {
80 | cast(void) _allocator.opPrefix!("-=")(cast(void[Node.sizeof])(*node), 1);
81 | }
82 | }();
83 | }
84 |
85 | pragma(inline, true)
86 | @nogc nothrow pure @trusted
87 | size_t opCmpPrefix(string op)(const Node *node, size_t val) const
88 | if ((op == "==") || (op == "<=") || (op == "<") || (op == ">=") || (op == ">"))
89 | {
90 | return _allocator.opCmpPrefix!op(cast(void[Node.sizeof])(*node), val);
91 | }
92 |
93 | static string immutableInsert(string stuff)
94 | {
95 | return q{
96 | _allocator = immutable AllocatorHandler(allocator);
97 | Node *tmpNode;
98 | Node *tmpHead;
99 | foreach (item; } ~ stuff ~ q{ )
100 | {
101 | Node *newNode;
102 | () @trusted { newNode =
103 | _allocator.make!(Node)(item, null, null);
104 | }();
105 | if (tmpHead is null)
106 | {
107 | tmpHead = tmpNode = newNode;
108 | }
109 | else
110 | {
111 | tmpNode._next = newNode;
112 | newNode._prev = tmpNode;
113 | addRef(newNode._prev);
114 | tmpNode = newNode;
115 | }
116 | }
117 | _head = (() @trusted => cast(immutable Node*)(tmpHead))();
118 | };
119 | }
120 |
121 | public:
122 | /**
123 | * Constructs a qualified doubly linked list that will use the provided
124 | * allocator object. For `immutable` objects, a `RCISharedAllocator` must
125 | * be supplied.
126 | *
127 | * Params:
128 | * allocator = a $(REF RCIAllocator, std,experimental,allocator) or
129 | * $(REF RCISharedAllocator, std,experimental,allocator)
130 | * allocator object
131 | *
132 | * Complexity: $(BIGOH 1)
133 | */
134 | this(A, this Q)(A allocator)
135 | if (!is(Q == shared)
136 | && (is(A == RCISharedAllocator) || !is(Q == immutable))
137 | && (is(A == RCIAllocator) || is(A == RCISharedAllocator)))
138 | {
139 | debug(CollectionDList)
140 | {
141 | writefln("DList.ctor: begin");
142 | scope(exit) writefln("DList.ctor: end");
143 | }
144 | static if (is(Q == immutable) || is(Q == const))
145 | {
146 | T[] empty;
147 | this(allocator, empty);
148 | }
149 | else
150 | {
151 | setAllocator(allocator);
152 | }
153 | }
154 |
155 | ///
156 | @safe unittest
157 | {
158 | auto dl = DList!int(theAllocator);
159 | auto cdl = const DList!int(processAllocator);
160 | auto idl = immutable DList!int(processAllocator);
161 | }
162 |
163 | /**
164 | * Constructs a qualified doubly linked list out of a number of items.
165 | * Because no allocator was provided, the list will use the
166 | * $(REF, GCAllocator, std,experimental,allocator).
167 | *
168 | * Params:
169 | * values = a variable number of items, either in the form of a
170 | * list or as a built-in array
171 | *
172 | * Complexity: $(BIGOH m), where `m` is the number of items.
173 | */
174 | this(U, this Q)(U[] values...)
175 | if (isImplicitlyConvertible!(U, T))
176 | {
177 | this(defaultAllocator!(typeof(this)), values);
178 | }
179 |
180 | ///
181 | static if (is(T == int))
182 | @safe unittest
183 | {
184 | import std.algorithm.comparison : equal;
185 |
186 | // Create a list from a list of ints
187 | {
188 | auto dl = DList!int(1, 2, 3);
189 | assert(equal(dl, [1, 2, 3]));
190 | }
191 | // Create a list from an array of ints
192 | {
193 | auto dl = DList!int([1, 2, 3]);
194 | assert(equal(dl, [1, 2, 3]));
195 | }
196 | // Create a list from a list from an input range
197 | {
198 | auto dl = DList!int(1, 2, 3);
199 | auto dl2 = DList!int(dl);
200 | assert(equal(dl2, [1, 2, 3]));
201 | }
202 | }
203 |
204 | /**
205 | * Constructs a qualified doubly linked list out of a number of items
206 | * that will use the provided allocator object.
207 | * For `immutable` objects, a `RCISharedAllocator` must be supplied.
208 | *
209 | * Params:
210 | * allocator = a $(REF RCIAllocator, std,experimental,allocator) or
211 | * $(REF RCISharedAllocator, std,experimental,allocator)
212 | * allocator object
213 | * values = a variable number of items, either in the form of a
214 | * list or as a built-in array
215 | *
216 | * Complexity: $(BIGOH m), where `m` is the number of items.
217 | */
218 | this(A, U, this Q)(A allocator, U[] values...)
219 | if (!is(Q == shared)
220 | && (is(A == RCISharedAllocator) || !is(Q == immutable))
221 | && (is(A == RCIAllocator) || is(A == RCISharedAllocator))
222 | && isImplicitlyConvertible!(U, T))
223 | {
224 | debug(CollectionDList)
225 | {
226 | writefln("DList.ctor: begin");
227 | scope(exit) writefln("DList.ctor: end");
228 | }
229 | static if (is(Q == immutable) || is(Q == const))
230 | {
231 | mixin(immutableInsert("values"));
232 | }
233 | else
234 | {
235 | setAllocator(allocator);
236 | insert(0, values);
237 | }
238 | }
239 |
240 | /**
241 | * Constructs a qualified doubly linked list out of an
242 | * $(REF_ALTTEXT input range, isInputRange, std,range,primitives).
243 | * Because no allocator was provided, the list will use the
244 | * $(REF, GCAllocator, std,experimental,allocator).
245 | *
246 | * Params:
247 | * stuff = an input range of elements that are implitictly convertible
248 | * to `T`
249 | *
250 | * Complexity: $(BIGOH m), where `m` is the number of elements in the range.
251 | */
252 | this(Stuff, this Q)(Stuff stuff)
253 | if (isInputRange!Stuff
254 | && isImplicitlyConvertible!(ElementType!Stuff, T)
255 | && !is(Stuff == T[]))
256 | {
257 | this(defaultAllocator!(typeof(this)), stuff);
258 | }
259 |
260 | /**
261 | * Constructs a qualified doubly linked list out of an
262 | * $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
263 | * that will use the provided allocator object.
264 | * For `immutable` objects, a `RCISharedAllocator` must be supplied.
265 | *
266 | * Params:
267 | * allocator = a $(REF RCIAllocator, std,experimental,allocator) or
268 | * $(REF RCISharedAllocator, std,experimental,allocator)
269 | * allocator object
270 | * stuff = an input range of elements that are implitictly convertible
271 | * to `T`
272 | *
273 | * Complexity: $(BIGOH m), where `m` is the number of elements in the range.
274 | */
275 | this(A, Stuff, this Q)(A allocator, Stuff stuff)
276 | if (!is(Q == shared)
277 | && (is(A == RCISharedAllocator) || !is(Q == immutable))
278 | && (is(A == RCIAllocator) || is(A == RCISharedAllocator))
279 | && isInputRange!Stuff
280 | && isImplicitlyConvertible!(ElementType!Stuff, T)
281 | && !is(Stuff == T[]))
282 | {
283 | debug(CollectionDList)
284 | {
285 | writefln("DList.ctor: begin");
286 | scope(exit) writefln("DList.ctor: end");
287 | }
288 | static if (is(Q == immutable) || is(Q == const))
289 | {
290 | mixin(immutableInsert("stuff"));
291 | }
292 | else
293 | {
294 | setAllocator(allocator);
295 | insert(0, stuff);
296 | }
297 | }
298 |
299 | this(this)
300 | {
301 | debug(CollectionDList)
302 | {
303 | writefln("DList.postblit: begin");
304 | scope(exit) writefln("DList.postblit: end");
305 | }
306 | _allocator.bootstrap();
307 | if (_head !is null)
308 | {
309 | addRef(_head);
310 | debug(CollectionDList) writefln("DList.postblit: Node %s has refcount: %s",
311 | _head._payload, *prefCount(_head));
312 | }
313 | }
314 |
315 | // Immutable ctors
316 | // Very important to pass the allocator by ref! (Related to postblit bug)
317 | private this(NodeQual, AllocQual, this Q)(NodeQual _newHead, ref AllocQual _newAllocator)
318 | if (is(typeof(_head) : typeof(_newHead))
319 | && (is(Q == immutable) || is(Q == const)))
320 | {
321 | _head = _newHead;
322 | // Needs a bootstrap
323 | // bootstrap is the equivalent of incRef
324 | _newAllocator.bootstrap();
325 | _allocator = _newAllocator;
326 | if (_head !is null)
327 | {
328 | addRef(_head);
329 | debug(CollectionDList) writefln("DList.ctor immutable: Node %s has "
330 | ~ "refcount: %s", _head._payload, *prefCount(_head));
331 | }
332 | }
333 |
334 | ~this()
335 | {
336 | debug(CollectionDList)
337 | {
338 | writefln("DList.dtor: Begin for instance %s of type %s",
339 | cast(size_t)(&this), typeof(this).stringof);
340 | scope(exit) writefln("DList.dtor: End for instance %s of type %s",
341 | cast(size_t)(&this), typeof(this).stringof);
342 | }
343 | if (_head !is null)
344 | {
345 | delRef(_head);
346 | if (_head !is null
347 | && ((_head._prev !is null) || (_head._next !is null)))
348 | {
349 | // If it was a single node list, only delRef must be used
350 | // in order to avoid premature/double freeing
351 | destroyUnused(_head);
352 | }
353 | }
354 | }
355 |
356 | static if (is(T == int))
357 | nothrow pure @safe unittest
358 | {
359 | auto s = DList!int(1, 2, 3);
360 |
361 | // Infer safety
362 | static assert(!__traits(compiles, () @safe { DList!Unsafe(Unsafe(1)); }));
363 | static assert(!__traits(compiles, () @safe { auto s = const DList!Unsafe(Unsafe(1)); }));
364 | static assert(!__traits(compiles, () @safe { auto s = immutable DList!Unsafe(Unsafe(1)); }));
365 |
366 | static assert(!__traits(compiles, () @safe { DList!UnsafeDtor(UnsafeDtor(1)); }));
367 | static assert(!__traits(compiles, () @safe { auto s = const DList!UnsafeDtor(UnsafeDtor(1)); }));
368 | static assert(!__traits(compiles, () @safe { auto s = immutable DList!UnsafeDtor(UnsafeDtor(1)); }));
369 |
370 | // Infer purity
371 | static assert(!__traits(compiles, () pure { DList!Impure(Impure(1)); }));
372 | static assert(!__traits(compiles, () pure { auto s = const DList!Impure(Impure(1)); }));
373 | static assert(!__traits(compiles, () pure { auto s = immutable DList!Impure(Impure(1)); }));
374 |
375 | static assert(!__traits(compiles, () pure { DList!ImpureDtor(ImpureDtor(1)); }));
376 | static assert(!__traits(compiles, () pure { auto s = const DList!ImpureDtor(ImpureDtor(1)); }));
377 | static assert(!__traits(compiles, () pure { auto s = immutable DList!ImpureDtor(ImpureDtor(1)); }));
378 |
379 | // Infer throwability
380 | static assert(!__traits(compiles, () nothrow { DList!Throws(Throws(1)); }));
381 | static assert(!__traits(compiles, () nothrow { auto s = const DList!Throws(Throws(1)); }));
382 | static assert(!__traits(compiles, () nothrow { auto s = immutable DList!Throws(Throws(1)); }));
383 |
384 | static assert(!__traits(compiles, () nothrow { DList!ThrowsDtor(ThrowsDtor(1)); }));
385 | static assert(!__traits(compiles, () nothrow { auto s = const DList!ThrowsDtor(ThrowsDtor(1)); }));
386 | static assert(!__traits(compiles, () nothrow { auto s = immutable DList!ThrowsDtor(ThrowsDtor(1)); }));
387 | }
388 |
389 | private void destroyUnused(Node *startNode)
390 | {
391 | debug(CollectionDList)
392 | {
393 | writefln("DList.destoryUnused: begin");
394 | scope(exit) writefln("DList.destoryUnused: end");
395 | }
396 |
397 | // Will be optimized away, but the type system infers T's safety
398 | if (0) { T t = T.init; }
399 |
400 | if (startNode is null) return;
401 |
402 | Node *tmpNode = startNode;
403 | bool isCycle = true;
404 | while (tmpNode !is null)
405 | {
406 | if (((tmpNode._next is null || tmpNode._prev is null)
407 | && opCmpPrefix!"=="(tmpNode, 0))
408 | || (tmpNode._next !is null && tmpNode._prev !is null
409 | && opCmpPrefix!"=="(tmpNode, 1)))
410 | {
411 | // The last node should always have rc == 0 (only one ref,
412 | // from prev._next)
413 | // The first node should always have rc == 0 (only one ref,
414 | // from next._prev), since we don't take into account
415 | // the head ref (that was deleted either by the dtor or by pop)
416 | // Nodes within the cycle should always have rc == 1
417 | tmpNode = tmpNode._next;
418 | }
419 | else
420 | {
421 | isCycle = false;
422 | break;
423 | }
424 | }
425 |
426 | tmpNode = startNode._prev;
427 | while (isCycle && tmpNode !is null)
428 | {
429 | if (((tmpNode._next is null || tmpNode._prev is null)
430 | && opCmpPrefix!"=="(tmpNode, 0))
431 | || (tmpNode._next !is null && tmpNode._prev !is null
432 | && opCmpPrefix!"=="(tmpNode, 1)))
433 | {
434 | tmpNode = tmpNode._prev;
435 | }
436 | else
437 | {
438 | isCycle = false;
439 | break;
440 | }
441 | }
442 |
443 | if (isCycle)
444 | {
445 | // We can safely deallocate memory
446 | // We could be in the middle of the list so we need to go both
447 | // forwards and backwards
448 | tmpNode = startNode._next;
449 | while (tmpNode !is null)
450 | {
451 | Node *oldNode = tmpNode;
452 | tmpNode = tmpNode._next;
453 | () @trusted { dispose(_allocator, oldNode); }();
454 | }
455 | tmpNode = startNode;
456 | while (tmpNode !is null)
457 | {
458 | Node *oldNode = tmpNode;
459 | tmpNode = tmpNode._prev;
460 | () @trusted { dispose(_allocator, oldNode); }();
461 | }
462 | }
463 | }
464 |
465 | /**
466 | * Check whether there are no more references to this list instance.
467 | *
468 | * Returns:
469 | * `true` if this is the only reference to this list instance;
470 | * `false` otherwise.
471 | *
472 | * Complexity: $(BIGOH n).
473 | */
474 | bool isUnique() const
475 | {
476 | debug(CollectionDList)
477 | {
478 | writefln("DList.isUnique: begin");
479 | scope(exit) writefln("DList.isUnique: end");
480 | }
481 |
482 | if (empty)
483 | {
484 | return true;
485 | }
486 |
487 | Node *tmpNode = (() @trusted => cast(Node*)_head)();
488 |
489 | // Rewind to the beginning of the list
490 | while (tmpNode !is null && tmpNode._prev !is null)
491 | {
492 | tmpNode = tmpNode._prev;
493 | }
494 |
495 | // For a single node list, head should have rc == 0
496 | if (tmpNode._next is null && opCmpPrefix!">"(tmpNode, 0))
497 | {
498 | return false;
499 | }
500 |
501 | while (tmpNode !is null)
502 | {
503 | if (tmpNode._next is null || tmpNode._prev is null)
504 | {
505 | // The first and last node should have rc == 0 unless the _head
506 | // is pointing to them, in which case rc must be 1
507 | if (((tmpNode is _head) && opCmpPrefix!">"(tmpNode, 1))
508 | || ((tmpNode !is _head) && opCmpPrefix!">"(tmpNode, 0)))
509 | {
510 | return false;
511 | }
512 | }
513 | else if (((tmpNode is _head) && opCmpPrefix!">"(tmpNode, 2))
514 | || ((tmpNode !is _head) && opCmpPrefix!">"(tmpNode, 1)))
515 | {
516 | // Any other node should have rc == 1 unless the _head
517 | // is pointing to it, in which case rc must be 2
518 | return false;
519 | }
520 | tmpNode = tmpNode._next;
521 | }
522 | return true;
523 | }
524 |
525 | ///
526 | static if (is(T == int))
527 | @safe unittest
528 | {
529 | auto dl = DList!int(24, 42);
530 | assert(dl.isUnique);
531 | {
532 | auto dl2 = dl;
533 | assert(!dl.isUnique);
534 | dl2.front = 0;
535 | assert(dl.front == 0);
536 | } // dl2 goes out of scope
537 | assert(dl.isUnique);
538 | }
539 |
540 | /**
541 | * Check if the list is empty.
542 | *
543 | * Returns:
544 | * `true` if there are no nodes in the list; `false` otherwise.
545 | *
546 | * Complexity: $(BIGOH 1).
547 | */
548 | @nogc nothrow pure @safe
549 | bool empty() const
550 | {
551 | return _head is null;
552 | }
553 |
554 | ///
555 | static if (is(T == int))
556 | @safe unittest
557 | {
558 | DList!int dl;
559 | assert(dl.empty);
560 | size_t pos = 0;
561 | dl.insert(pos, 1);
562 | assert(!dl.empty);
563 | }
564 |
565 | /**
566 | * Provide access to the first element in the list. The user must check
567 | * that the list isn't `empty`, prior to calling this function.
568 | *
569 | * Returns:
570 | * a reference to the first element.
571 | *
572 | * Complexity: $(BIGOH 1).
573 | */
574 | ref auto front(this _)()
575 | {
576 | assert(!empty, "DList.front: List is empty");
577 | return _head._payload;
578 | }
579 |
580 | ///
581 | static if (is(T == int))
582 | @safe unittest
583 | {
584 | auto dl = DList!int(1, 2, 3);
585 | assert(dl.front == 1);
586 | dl.front = 0;
587 | assert(dl.front == 0);
588 | }
589 |
590 | /**
591 | * Advance to the next element in the list. The user must check
592 | * that the list isn't `empty`, prior to calling this function.
593 | *
594 | * If this was the last element in the list and there are no more
595 | * references to the current list, then the list and all it's elements
596 | * will be destroyed; this will call `T`'s dtor, if one is defined,
597 | * and will collect the resources.
598 | *
599 | * Complexity: usually $(BIGOH 1), worst case $(BIGOH n).
600 | */
601 | void popFront()
602 | {
603 | debug(CollectionDList)
604 | {
605 | writefln("DList.popFront: begin");
606 | scope(exit) writefln("DList.popFront: end");
607 | }
608 | assert(!empty, "DList.popFront: List is empty");
609 | Node *tmpNode = _head;
610 | _head = _head._next;
611 | if (_head !is null)
612 | {
613 | addRef(_head);
614 | delRef(tmpNode);
615 | }
616 | else
617 | {
618 | delRef(tmpNode);
619 | if (tmpNode !is null
620 | && ((tmpNode._prev !is null) || (tmpNode._next !is null)))
621 | {
622 | // If it was a single node list, only delRef must be used
623 | // in order to avoid premature/double freeing
624 | destroyUnused(tmpNode);
625 | }
626 | }
627 | }
628 |
629 | ///
630 | static if (is(T == int))
631 | @safe unittest
632 | {
633 | auto a = [1, 2, 3];
634 | auto dl = DList!int(a);
635 | size_t i = 0;
636 | while (!dl.empty)
637 | {
638 | assert(dl.front == a[i++]);
639 | dl.popFront;
640 | }
641 | assert(dl.empty);
642 | }
643 |
644 | /**
645 | * Go to the previous element in the list. The user must check
646 | * that the list isn't `empty`, prior to calling this function.
647 | *
648 | * If this was the first element in the list and there are no more
649 | * references to the current list, then the list and all it's elements
650 | * will be destroyed; this will call `T`'s dtor, if one is defined,
651 | * and will collect the resources.
652 | *
653 | * Complexity: usually $(BIGOH 1), worst case $(BIGOH n).
654 | */
655 | void popPrev()
656 | {
657 | debug(CollectionDList)
658 | {
659 | writefln("DList.popPrev: begin");
660 | scope(exit) writefln("DList.popPrev: end");
661 | }
662 | assert(!empty, "DList.popPrev: List is empty");
663 | Node *tmpNode = _head;
664 | _head = _head._prev;
665 | if (_head !is null) {
666 | addRef(_head);
667 | delRef(tmpNode);
668 | }
669 | else
670 | {
671 | delRef(tmpNode);
672 | if (tmpNode !is null
673 | && ((tmpNode._prev !is null) || (tmpNode._next !is null)))
674 | {
675 | // If it was a single node list, only delRef must be used
676 | // in order to avoid premature/double freeing
677 | destroyUnused(tmpNode);
678 | }
679 | }
680 | }
681 |
682 | ///
683 | static if (is(T == int))
684 | @safe unittest
685 | {
686 | auto dl = DList!int([1, 2, 3]);
687 | dl.popFront;
688 | assert(dl.front == 2);
689 | dl.popPrev;
690 | assert(dl.front == 1);
691 | dl.popPrev;
692 | assert(dl.empty);
693 | }
694 |
695 | /**
696 | * Advance to the next element in the list. The user must check
697 | * that the list isn't `empty`, prior to calling this function.
698 | *
699 | * This must be used in order to iterate through a `const` or `immutable`
700 | * list. For a mutable list this is equivalent to calling `popFront`.
701 | *
702 | * Returns:
703 | * a list that starts with the next element in the original list
704 | *
705 | * Complexity: $(BIGOH 1).
706 | */
707 | Qualified tail(this Qualified)()
708 | {
709 | debug(CollectionDList)
710 | {
711 | writefln("DList.popFront: begin");
712 | scope(exit) writefln("DList.popFront: end");
713 | }
714 | assert(!empty, "DList.popFront: List is empty");
715 |
716 | static if (is(Qualified == immutable) || is(Qualified == const))
717 | {
718 | return typeof(this)(_head._next, _allocator);
719 | }
720 | else
721 | {
722 | return .tail(this);
723 | }
724 | }
725 |
726 | ///
727 | static if (is(T == int))
728 | @safe unittest
729 | {
730 | auto idl = immutable DList!int([1, 2, 3]);
731 | assert(idl.tail.front == 2);
732 | }
733 |
734 | /**
735 | * Eagerly iterate over each element in the list and call `fun` over each
736 | * element. This should be used to iterate through `const` and `immutable`
737 | * lists.
738 | *
739 | * Normally, the entire list is iterated. If partial iteration (early stopping)
740 | * is desired, `fun` needs to return a value of type
741 | * $(REF Flag, std,typecons)`!"each"` (`Yes.each` to continue iteration, or
742 | * `No.each` to stop).
743 | *
744 | * Params:
745 | * fun = unary function to apply on each element of the list.
746 | *
747 | * Returns:
748 | * `Yes.each` if it has iterated through all the elements in the list,
749 | * or `No.each` otherwise.
750 | *
751 | * Complexity: $(BIGOH n).
752 | */
753 | template each(alias fun)
754 | {
755 | import std.typecons : Flag, Yes, No;
756 | import std.functional : unaryFun;
757 | import stdx.collections.slist : SList;
758 |
759 | Flag!"each" each(this Q)()
760 | if (is (typeof(unaryFun!fun(T.init))))
761 | {
762 | alias fn = unaryFun!fun;
763 |
764 | auto sl = SList!(const DList!T)(this);
765 | while (!sl.empty && !sl.front.empty)
766 | {
767 | static if (!is(typeof(fn(T.init)) == Flag!"each"))
768 | {
769 | cast(void) fn(sl.front.front);
770 | }
771 | else
772 | {
773 | if (fn(sl.front.front) == No.each)
774 | return No.each;
775 | }
776 | sl ~= sl.front.tail;
777 | sl.popFront;
778 | }
779 | return Yes.each;
780 | }
781 | }
782 |
783 | ///
784 | static if (is(T == int))
785 | @safe unittest
786 | {
787 | import std.typecons : Flag, Yes, No;
788 |
789 | auto idl = immutable DList!int([1, 2, 3]);
790 |
791 | static bool foo(int x) { return x > 0; }
792 |
793 | assert(idl.each!foo == Yes.each);
794 | }
795 |
796 | /**
797 | * Perform a shallow copy of the list.
798 | *
799 | * Returns:
800 | * a new reference to the current list.
801 | *
802 | * Complexity: $(BIGOH 1).
803 | */
804 | ref Qualified save(this Qualified)()
805 | {
806 | debug(CollectionDList)
807 | {
808 | writefln("DList.save: begin");
809 | scope(exit) writefln("DList.save: end");
810 | }
811 | return this;
812 | }
813 |
814 | ///
815 | static if (is(T == int))
816 | @safe unittest
817 | {
818 | auto a = [1, 2, 3];
819 | auto dl = DList!int(a);
820 | size_t i = 0;
821 |
822 | auto tmp = dl.save;
823 | while (!tmp.empty)
824 | {
825 | assert(tmp.front == a[i++]);
826 | tmp.popFront;
827 | }
828 | assert(tmp.empty);
829 | assert(!dl.empty);
830 | }
831 |
832 | /**
833 | * Perform a copy of the list. This will create a new list that will copy
834 | * the elements of the current list. This will `NOT` call `dup` on the
835 | * elements of the list, regardless if `T` defines it or not.
836 | *
837 | * Returns:
838 | * a new list.
839 | *
840 | * Complexity: $(BIGOH n).
841 | */
842 | typeof(this) dup()
843 | {
844 | debug(CollectionDList)
845 | {
846 | writefln("DList.dup: begin");
847 | scope(exit) writefln("DList.dup: end");
848 | }
849 |
850 | DList!T result;
851 | result._allocator = _allocator;
852 |
853 | // TODO: this should rewind the list
854 | static if (is(Q == immutable) || is(Q == const))
855 | {
856 | auto tmp = this;
857 | while(!tmp.empty)
858 | {
859 | result ~= tmp.front;
860 | tmp = tmp.tail;
861 | }
862 | }
863 | else
864 | {
865 | result.insert(0, this);
866 | }
867 | return result;
868 | }
869 |
870 | ///
871 | static if (is(T == int))
872 | @safe unittest
873 | {
874 | import std.algorithm.comparison : equal;
875 |
876 | auto dl = DList!int(1, 2, 3);
877 | auto dlDup = dl.dup;
878 | assert(equal(dl, dlDup));
879 | dlDup.front = 0;
880 | assert(!equal(dl, dlDup));
881 | assert(dlDup.front == 0);
882 | assert(dl.front == 1);
883 | }
884 |
885 | /**
886 | * Inserts the elements of an
887 | * $(REF_ALTTEXT input range, isInputRange, std,range,primitives), or a
888 | * variable number of items, at the given `pos`.
889 | *
890 | * If no allocator was provided when the list was created, the
891 | * $(REF, GCAllocator, std,experimental,allocator) will be used.
892 | *
893 | * Params:
894 | * pos = a positive integral
895 | * stuff = an input range of elements that are implitictly convertible
896 | * to `T`; a variable number of items either in the form of a
897 | * list or as a built-in array
898 | *
899 | * Returns:
900 | * the number of elements inserted
901 | *
902 | * Complexity: $(BIGOH pos + m), where `m` is the number of elements in the range.
903 | */
904 | size_t insert(Stuff)(size_t pos, Stuff stuff)
905 | if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
906 | {
907 | debug(CollectionDList)
908 | {
909 | writefln("DList.insert: begin");
910 | scope(exit) writefln("DList.insert: end");
911 | }
912 |
913 | // Will be optimized away, but the type system infers T's safety
914 | if (0) { T t = T.init; }
915 |
916 | // Ensure we have an allocator. If it was already set, this will do nothing
917 | auto a = threadAllocatorObject();
918 | setAllocator(a);
919 |
920 | size_t result;
921 | Node *tmpNode;
922 | Node *tmpHead;
923 | foreach (item; stuff)
924 | {
925 | Node *newNode;
926 | () @trusted { newNode = _allocator.make!(Node)(item, null, null); }();
927 | if (tmpHead is null)
928 | {
929 | tmpHead = tmpNode = newNode;
930 | }
931 | else
932 | {
933 | tmpNode._next = newNode;
934 | newNode._prev = tmpNode;
935 | addRef(newNode._prev);
936 | tmpNode = newNode;
937 | }
938 | ++result;
939 | }
940 |
941 | if (!tmpNode)
942 | {
943 | return 0;
944 | }
945 |
946 | if (!_head) assert(pos == 0);
947 |
948 | size_t initPos = pos;
949 | Node *needle = _head;
950 | while (pos && needle._next !is null)
951 | {
952 | needle = needle._next;
953 | --pos;
954 | }
955 |
956 | // Check if we need to insert at the back of the list
957 | if (initPos != 0 && needle._next is null && pos >= 1)
958 | {
959 | // We need to insert at the back of the list
960 | assert(pos == 1, "Index out of range");
961 | needle._next = tmpHead;
962 | tmpHead._prev = needle;
963 | addRef(needle);
964 | return result;
965 | }
966 | assert(pos == 0, "Index out of range");
967 |
968 | tmpNode._next = needle;
969 | if (needle !is null)
970 | {
971 | addRef(needle);
972 | if (needle._prev !is null)
973 | {
974 | tmpHead._prev = needle._prev;
975 | needle._prev._next = tmpHead;
976 | // Inc ref only when tmpHead will be the new head of the list
977 | if (initPos == 0)
978 | {
979 | addRef(tmpHead);
980 | }
981 |
982 | // Delete extra ref, since we already added the ref earlier
983 | // through tmpNode._next
984 | delRef(needle);
985 | }
986 | if (initPos == 0)
987 | {
988 | // Pass the ref to the new head
989 | delRef(needle);
990 | }
991 | assert(needle !is null);
992 | needle._prev = tmpNode;
993 | if (tmpHead == tmpNode)
994 | {
995 | addRef(tmpHead);
996 | }
997 | else
998 | {
999 | addRef(needle._prev);
1000 | }
1001 | }
1002 |
1003 | if (initPos == 0)
1004 | {
1005 | _head = tmpHead;
1006 | }
1007 | return result;
1008 | }
1009 |
1010 | /// ditto
1011 | size_t insert(Stuff)(size_t pos, Stuff[] stuff...)
1012 | if (isImplicitlyConvertible!(Stuff, T))
1013 | {
1014 | return insert(pos, stuff);
1015 | }
1016 |
1017 | ///
1018 | static if (is(T == int))
1019 | @safe unittest
1020 | {
1021 | import std.algorithm.comparison : equal;
1022 |
1023 | auto d = DList!int(4, 5);
1024 | DList!int dl;
1025 | assert(dl.empty);
1026 |
1027 | size_t pos = 0;
1028 | pos += dl.insert(pos, 1);
1029 | pos += dl.insert(pos, [2, 3]);
1030 | assert(equal(dl, [1, 2, 3]));
1031 |
1032 | // insert from an input range
1033 | pos += dl.insert(pos, d);
1034 | assert(equal(dl, [1, 2, 3, 4, 5]));
1035 | d.front = 0;
1036 | assert(equal(dl, [1, 2, 3, 4, 5]));
1037 | }
1038 |
1039 | /**
1040 | * Inserts the elements of an
1041 | * $(REF_ALTTEXT input range, isInputRange, std,range,primitives), or a
1042 | * variable number of items, at the end of the list.
1043 | *
1044 | * If no allocator was provided when the list was created, the
1045 | * $(REF, GCAllocator, std,experimental,allocator) will be used.
1046 | *
1047 | * Params:
1048 | * stuff = an input range of elements that are implitictly convertible
1049 | * to `T`; a variable number of items either in the form of a
1050 | * list or as a built-in array
1051 | *
1052 | * Returns:
1053 | * the number of elements inserted
1054 | *
1055 | * Complexity: $(BIGOH pos + m), where `m` is the number of elements in the range.
1056 | */
1057 | size_t insertBack(Stuff)(Stuff stuff)
1058 | if (isInputRange!Stuff && isImplicitlyConvertible!(ElementType!Stuff, T))
1059 | {
1060 | debug(CollectionDList)
1061 | {
1062 | writefln("DList.insertBack: begin");
1063 | scope(exit) writefln("DList.insertBack: end");
1064 | }
1065 |
1066 | // Will be optimized away, but the type system infers T's safety
1067 | if (0) { T t = T.init; }
1068 |
1069 | // Ensure we have an allocator. If it was already set, this will do nothing
1070 | auto a = threadAllocatorObject();
1071 | setAllocator(a);
1072 |
1073 | size_t result;
1074 | Node *tmpNode;
1075 | Node *tmpHead;
1076 | foreach (item; stuff)
1077 | {
1078 | Node *newNode;
1079 | () @trusted { newNode = _allocator.make!(Node)(item, null, null); }();
1080 | if (tmpHead is null)
1081 | {
1082 | tmpHead = tmpNode = newNode;
1083 | }
1084 | else
1085 | {
1086 | tmpNode._next = newNode;
1087 | newNode._prev = tmpNode;
1088 | addRef(newNode._prev);
1089 | tmpNode = newNode;
1090 | }
1091 | ++result;
1092 | }
1093 |
1094 | if (!tmpNode)
1095 | {
1096 | return 0;
1097 | }
1098 |
1099 | if (_head is null)
1100 | {
1101 | _head = tmpHead;
1102 | }
1103 | else
1104 | {
1105 | Node *endNode;
1106 | for (endNode = _head; endNode._next !is null; endNode = endNode._next) { }
1107 | endNode._next = tmpHead;
1108 | // don't addRef(tmpHead) since the ref will pass from tmpHead to
1109 | // endNode._next when tmpHead's scope ends
1110 | tmpHead._prev = endNode;
1111 | addRef(endNode);
1112 | }
1113 |
1114 | return result;
1115 | }
1116 |
1117 | /// ditto
1118 | size_t insertBack(Stuff)(Stuff[] stuff...)
1119 | if (isImplicitlyConvertible!(Stuff, T))
1120 | {
1121 | return insertBack(stuff);
1122 | }
1123 |
1124 | ///
1125 | static if (is(T == int))
1126 | @safe unittest
1127 | {
1128 | import std.algorithm.comparison : equal;
1129 |
1130 | auto d = DList!int(4, 5);
1131 | DList!int dl;
1132 | assert(dl.empty);
1133 |
1134 | dl.insertBack(1);
1135 | dl.insertBack([2, 3]);
1136 | assert(equal(dl, [1, 2, 3]));
1137 |
1138 | // insert from an input range
1139 | dl.insertBack(d);
1140 | assert(equal(dl, [1, 2, 3, 4, 5]));
1141 | d.front = 0;
1142 | assert(equal(dl, [1, 2, 3, 4, 5]));
1143 | }
1144 |
1145 | /**
1146 | * Create a new list that results from the concatenation of this list
1147 | * with `rhs`.
1148 | *
1149 | * Params:
1150 | * rhs = can be an element that is implicitly convertible to `T`, an
1151 | * input range of such elements, or another doubly linked list
1152 | *
1153 | * Returns:
1154 | * the newly created list
1155 | *
1156 | * Complexity: $(BIGOH n + m), where `m` is the number of elements in `rhs`.
1157 | */
1158 | auto ref opBinary(string op, U)(auto ref U rhs)
1159 | if (op == "~" &&
1160 | //(is (U : const typeof(this))
1161 | (is (U == typeof(this))
1162 | || is (U : T)
1163 | || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
1164 | ))
1165 | {
1166 | debug(CollectionDList)
1167 | {
1168 | writefln("DList.opBinary!~: begin");
1169 | scope(exit) writefln("DList.opBinary!~: end");
1170 | }
1171 |
1172 | auto newList = this.dup();
1173 | newList.insertBack(rhs);
1174 | return newList;
1175 | }
1176 |
1177 | ///
1178 | static if (is(T == int))
1179 | @safe unittest
1180 | {
1181 | import std.algorithm.comparison : equal;
1182 |
1183 | auto dl = DList!int(1);
1184 | auto dl2 = dl ~ 2;
1185 |
1186 | assert(equal(dl2, [1, 2]));
1187 | dl.front = 0;
1188 | assert(equal(dl2, [1, 2]));
1189 | }
1190 |
1191 | /**
1192 | * Assign `rhs` to this list. The current list will now become another
1193 | * reference to `rhs`, unless `rhs` is `null`, in which case the current
1194 | * list will become empty. If `rhs` refers to the current list nothing will
1195 | * happen.
1196 | *
1197 | * All the previous list elements that have no more references to them
1198 | * will be destroyed; this leads to a $(BIGOH n) complexity.
1199 | *
1200 | * Params:
1201 | * rhs = a reference to a doubly linked list
1202 | *
1203 | * Returns:
1204 | * a reference to this list
1205 | *
1206 | * Complexity: $(BIGOH n).
1207 | */
1208 | auto ref opAssign()(auto ref typeof(this) rhs)
1209 | {
1210 | debug(CollectionDList)
1211 | {
1212 | writefln("DList.opAssign: begin");
1213 | scope(exit) writefln("DList.opAssign: end");
1214 | }
1215 |
1216 | if (rhs._head !is null && _head is rhs._head)
1217 | {
1218 | return this;
1219 | }
1220 |
1221 | if (rhs._head !is null)
1222 | {
1223 | rhs.addRef(rhs._head);
1224 | debug(CollectionDList) writefln("DList.opAssign: Node %s has refcount: %s",
1225 | rhs._head._payload, *localPrefCount(rhs._head));
1226 | }
1227 |
1228 | if (_head !is null)
1229 | {
1230 | Node *tmpNode = _head;
1231 | delRef(tmpNode);
1232 | if (tmpNode !is null
1233 | && ((tmpNode._prev !is null) || (tmpNode._next !is null)))
1234 | {
1235 | // If it was a single node list, only delRef must be used
1236 | // in order to avoid premature/double freeing
1237 | destroyUnused(tmpNode);
1238 | }
1239 | }
1240 | _head = rhs._head;
1241 | _allocator = rhs._allocator;
1242 | return this;
1243 | }
1244 |
1245 | ///
1246 | static if (is(T == int))
1247 | @safe unittest
1248 | {
1249 | import std.algorithm.comparison : equal;
1250 |
1251 | auto dl = DList!int(1);
1252 | auto dl2 = DList!int(1, 2);
1253 |
1254 | dl = dl2; // this will free the old dl
1255 | assert(equal(dl, [1, 2]));
1256 | dl.front = 0;
1257 | assert(equal(dl2, [0, 2]));
1258 | }
1259 |
1260 | /**
1261 | * Append the elements of `rhs` at the end of the list.
1262 | *
1263 | * If no allocator was provided when the list was created, the
1264 | * $(REF, GCAllocator, std,experimental,allocator) will be used.
1265 | *
1266 | * Params:
1267 | * rhs = can be an element that is implicitly convertible to `T`, an
1268 | * input range of such elements, or another doubly linked list
1269 | *
1270 | * Returns:
1271 | * a reference to this list
1272 | *
1273 | * Complexity: $(BIGOH n + m), where `m` is the number of elements in `rhs`.
1274 | */
1275 | auto ref opOpAssign(string op, U)(auto ref U rhs)
1276 | if (op == "~" &&
1277 | (is (U == typeof(this))
1278 | || is (U : T)
1279 | || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T))
1280 | ))
1281 | {
1282 | debug(CollectionDList)
1283 | {
1284 | writefln("DList.opOpAssign!~: %s begin", typeof(this).stringof);
1285 | scope(exit) writefln("DList.opOpAssign!~: %s end", typeof(this).stringof);
1286 | }
1287 |
1288 | insertBack(rhs);
1289 | return this;
1290 | }
1291 |
1292 | ///
1293 | static if (is(T == int))
1294 | @safe unittest
1295 | {
1296 | import std.algorithm.comparison : equal;
1297 |
1298 | auto d = DList!int(4, 5);
1299 | DList!int dl;
1300 | assert(dl.empty);
1301 |
1302 | dl ~= 1;
1303 | dl ~= [2, 3];
1304 | assert(equal(dl, [1, 2, 3]));
1305 |
1306 | // append an input range
1307 | dl ~= d;
1308 | assert(equal(dl, [1, 2, 3, 4, 5]));
1309 | d.front = 0;
1310 | assert(equal(dl, [1, 2, 3, 4, 5]));
1311 | }
1312 |
1313 | /**
1314 | * Remove the current element from the list. If there are no
1315 | * more references to the current element, then it will be destroyed.
1316 | */
1317 | void remove()
1318 | {
1319 | debug(CollectionDList)
1320 | {
1321 | writefln("DList.remove: begin");
1322 | scope(exit) writefln("DList.remove: end");
1323 | }
1324 | assert(!empty, "DList.remove: List is empty");
1325 |
1326 | Node *tmpNode = _head;
1327 | _head = _head._next;
1328 | if (_head !is null)
1329 | {
1330 | //addRef(_head);
1331 | _head._prev = tmpNode._prev;
1332 | delRef(tmpNode); // Remove tmpNode._next._prev ref
1333 | tmpNode._next = null;
1334 | //delRef(_head);
1335 | if (tmpNode._prev !is null)
1336 | {
1337 | addRef(_head);
1338 | tmpNode._prev._next = _head;
1339 | delRef(tmpNode); // Remove tmpNode._prev._next ref
1340 | tmpNode._prev = null;
1341 | }
1342 | }
1343 | else if (tmpNode._prev !is null)
1344 | {
1345 | _head = tmpNode._prev;
1346 | //addRef(_head);
1347 | tmpNode._prev = null;
1348 | //delRef(_head);
1349 | _head._next = null;
1350 | delRef(tmpNode);
1351 | }
1352 | delRef(tmpNode); // Remove old head ref
1353 | if (tmpNode !is null
1354 | && ((tmpNode._prev !is null) || (tmpNode._next !is null)))
1355 | {
1356 | // If it was a single node list, only delRef must be used
1357 | // in order to avoid premature/double freeing
1358 | destroyUnused(tmpNode);
1359 | }
1360 | }
1361 |
1362 | ///
1363 | static if (is(T == int))
1364 | @safe unittest
1365 | {
1366 | import std.algorithm.comparison : equal;
1367 |
1368 | auto dl = DList!int(1, 2, 3);
1369 | auto dl2 = dl;
1370 |
1371 | assert(equal(dl, [1, 2, 3]));
1372 | dl.popFront;
1373 | dl.remove();
1374 | assert(equal(dl, [3]));
1375 | assert(equal(dl2, [1, 3]));
1376 | dl.popPrev;
1377 | assert(equal(dl, [1, 3]));
1378 | }
1379 |
1380 | debug(CollectionDList)
1381 | void printRefCount(Node *sn = null)
1382 | {
1383 | import std.stdio;
1384 | writefln("DList.printRefCount: begin");
1385 | scope(exit) writefln("DList.printRefCount: end");
1386 |
1387 | Node *tmpNode;
1388 | if (sn is null)
1389 | tmpNode = _head;
1390 | else
1391 | tmpNode = sn;
1392 |
1393 | while (tmpNode !is null && tmpNode._prev !is null)
1394 | {
1395 | // Rewind to the beginning of the list
1396 | tmpNode = tmpNode._prev;
1397 | }
1398 | while (tmpNode !is null)
1399 | {
1400 | writefln("DList.printRefCount: Node %s has ref count %s",
1401 | tmpNode._payload, *localPrefCount(tmpNode));
1402 | tmpNode = tmpNode._next;
1403 | }
1404 | }
1405 | }
1406 |
1407 | version (unittest) private nothrow pure @safe
1408 | void testInit(RCIAllocator allocator)
1409 | {
1410 | import std.algorithm.comparison : equal;
1411 |
1412 | DList!int dl = DList!int(allocator);
1413 | assert(dl.empty);
1414 | assert(dl.isUnique);
1415 | int[] empty;
1416 | assert(equal(dl, empty));
1417 |
1418 | DList!int dl2 = DList!int(allocator, 1);
1419 | assert(equal(dl2, [1]));
1420 |
1421 | DList!int dl3 = DList!int(allocator, 1, 2);
1422 | assert(equal(dl3, [1, 2]));
1423 |
1424 | DList!int dl4 = DList!int(allocator, [1]);
1425 | assert(equal(dl4, [1]));
1426 |
1427 | DList!int dl5 = DList!int(allocator, [1, 2]);
1428 | assert(equal(dl5, [1, 2]));
1429 | }
1430 |
1431 | @safe unittest
1432 | {
1433 | import std.conv;
1434 | SCAlloc statsCollectorAlloc;
1435 | {
1436 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1437 | () nothrow pure @safe {
1438 | testInit(_allocator);
1439 | }();
1440 | }
1441 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1442 | assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1443 | ~ to!string(bytesUsed) ~ " bytes");
1444 | }
1445 |
1446 | version (unittest) private nothrow pure @safe
1447 | void testInsert(RCIAllocator allocator)
1448 | {
1449 | import std.algorithm.comparison : equal;
1450 | import std.range.primitives : walkLength;
1451 |
1452 | DList!int dl = DList!int(allocator, 1);
1453 | size_t pos = 0;
1454 | dl.insert(pos, 2);
1455 | assert(equal(dl, [2, 1]));
1456 |
1457 | DList!int dl2 = DList!int(allocator, 1);
1458 | dl2.insert(pos, 2, 3);
1459 | assert(equal(dl2, [2, 3, 1]));
1460 |
1461 | DList!int dl3 = DList!int(allocator, 1, 2);
1462 | dl3.insert(pos, 3);
1463 | assert(equal(dl3, [3, 1, 2]));
1464 |
1465 | DList!int dl4 = DList!int(allocator, 1, 2);
1466 | dl4.insert(pos, 3, 4);
1467 | assert(equal(dl4, [3, 4, 1, 2]));
1468 |
1469 | DList!int dl5 = DList!int(allocator, 1, 2);
1470 | dl5.popFront();
1471 | dl5.insert(pos, 3);
1472 | assert(equal(dl5, [3, 2]));
1473 | dl5.popPrev();
1474 | assert(equal(dl5, [1, 3, 2]));
1475 |
1476 | DList!int dl6 = DList!int(allocator, 1, 2);
1477 | dl6.popFront();
1478 | dl6.insert(pos, 3, 4);
1479 | assert(equal(dl6, [3, 4, 2]));
1480 | dl6.popPrev();
1481 | assert(equal(dl6, [1, 3, 4, 2]));
1482 | dl6.insertBack(5);
1483 | assert(equal(dl6, [1, 3, 4, 2, 5]));
1484 | dl6.insertBack(6, 7);
1485 | assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7]));
1486 | dl6.insertBack([8]);
1487 | assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8]));
1488 | dl6.insertBack([9, 10]);
1489 | assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10]));
1490 | int[] empty;
1491 | dl6.insertBack(empty);
1492 | assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10]));
1493 | dl6.insert(pos, empty);
1494 | assert(equal(dl6, [1, 3, 4, 2, 5, 6, 7, 8, 9, 10]));
1495 |
1496 | DList!int dl7 = DList!int(allocator, 1);
1497 | assert(equal(dl7, [1]));
1498 | dl7.insert(pos, 2);
1499 | assert(equal(dl7, [2, 1]));
1500 | pos = walkLength(dl7);
1501 | dl7.insert(pos, 3);
1502 | assert(equal(dl7, [2, 1, 3]));
1503 | dl7.insert(pos, 4);
1504 | assert(equal(dl7, [2, 1, 4, 3]));
1505 | }
1506 |
1507 | @safe unittest
1508 | {
1509 | import std.conv;
1510 | SCAlloc statsCollectorAlloc;
1511 | {
1512 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1513 | () nothrow pure @safe {
1514 | testInsert(_allocator);
1515 | }();
1516 | }
1517 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1518 | assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1519 | ~ to!string(bytesUsed) ~ " bytes");
1520 | }
1521 |
1522 | version (unittest) private nothrow pure @safe
1523 | void testRemove(RCIAllocator allocator)
1524 | {
1525 | import std.algorithm.comparison : equal;
1526 |
1527 | DList!int dl = DList!int(allocator, 1);
1528 | size_t pos = 0;
1529 | dl.remove();
1530 | assert(dl.empty);
1531 | assert(dl.isUnique);
1532 | assert(!dl._allocator.isNull);
1533 |
1534 | dl.insert(pos, 2);
1535 | auto dl2 = dl;
1536 | auto dl3 = dl;
1537 | assert(!dl.isUnique);
1538 |
1539 | dl.popFront();
1540 | assert(dl.empty);
1541 | assert(!dl._allocator.isNull);
1542 |
1543 | dl2.popPrev();
1544 | assert(dl2.empty);
1545 | assert(dl3.isUnique);
1546 |
1547 | auto dl4 = dl3;
1548 | assert(!dl3.isUnique);
1549 | dl4.remove();
1550 | assert(dl4.empty);
1551 | assert(!dl3.empty);
1552 | assert(dl3.isUnique);
1553 | }
1554 |
1555 | @safe unittest
1556 | {
1557 | import std.conv;
1558 | SCAlloc statsCollectorAlloc;
1559 | {
1560 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1561 | () nothrow pure @safe {
1562 | testRemove(_allocator);
1563 | }();
1564 | }
1565 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1566 | assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1567 | ~ to!string(bytesUsed) ~ " bytes");
1568 | }
1569 |
1570 | version (unittest) private nothrow pure @safe
1571 | void testCopyAndRef(RCIAllocator allocator)
1572 | {
1573 | import std.algorithm.comparison : equal;
1574 |
1575 | auto dlFromList = DList!int(allocator, 1, 2, 3);
1576 | auto dlFromRange = DList!int(allocator, dlFromList);
1577 | assert(equal(dlFromList, dlFromRange));
1578 |
1579 | dlFromList.popFront();
1580 | assert(equal(dlFromList, [2, 3]));
1581 | assert(equal(dlFromRange, [1, 2, 3]));
1582 |
1583 | DList!int dlInsFromRange = DList!int(allocator);
1584 | size_t pos = 0;
1585 | dlInsFromRange.insert(pos, dlFromList);
1586 | dlFromList.popFront();
1587 | assert(equal(dlFromList, [3]));
1588 | assert(equal(dlInsFromRange, [2, 3]));
1589 |
1590 | DList!int dlInsBackFromRange = DList!int(allocator);
1591 | dlInsBackFromRange.insert(pos, dlFromList);
1592 | dlFromList.popFront();
1593 | assert(dlFromList.empty);
1594 | assert(equal(dlInsBackFromRange, [3]));
1595 |
1596 | auto dlFromRef = dlInsFromRange;
1597 | auto dlFromDup = dlInsFromRange.dup;
1598 | assert(dlInsFromRange.front == 2);
1599 | dlFromRef.front = 5;
1600 | assert(dlInsFromRange.front == 5);
1601 | assert(dlFromDup.front == 2);
1602 | }
1603 |
1604 | @safe unittest
1605 | {
1606 | import std.conv;
1607 | SCAlloc statsCollectorAlloc;
1608 | {
1609 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1610 | () nothrow pure @safe {
1611 | testCopyAndRef(_allocator);
1612 | }();
1613 | }
1614 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1615 | assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1616 | ~ to!string(bytesUsed) ~ " bytes");
1617 | }
1618 |
1619 | @safe unittest
1620 | {
1621 | import std.algorithm.comparison : equal;
1622 |
1623 | SCAlloc statsCollectorAlloc;
1624 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1625 |
1626 | DList!int dl = DList!int(_allocator, 1, 2, 3);
1627 | auto before = statsCollectorAlloc.bytesUsed;
1628 | {
1629 | DList!int dl2 = dl;
1630 | dl2.popFront();
1631 | assert(equal(dl2, [2, 3]));
1632 | }
1633 | assert(before == statsCollectorAlloc.bytesUsed);
1634 | assert(equal(dl, [1, 2, 3]));
1635 | dl.tail();
1636 | }
1637 |
1638 | version(unittest) private nothrow pure @safe
1639 | void testImmutability(RCISharedAllocator allocator)
1640 | {
1641 | auto s = immutable DList!(int)(allocator, 1, 2, 3);
1642 | auto s2 = s;
1643 | auto s3 = s2.save();
1644 |
1645 | assert(s2.front == 1);
1646 | static assert(!__traits(compiles, s2.front = 4));
1647 | static assert(!__traits(compiles, s2.popFront()));
1648 |
1649 | auto s4 = s2.tail;
1650 | assert(s4.front == 2);
1651 | static assert(!__traits(compiles, s4 = s4.tail));
1652 | }
1653 |
1654 | version(unittest) private nothrow pure @safe
1655 | void testConstness(RCISharedAllocator allocator)
1656 | {
1657 | auto s = const DList!(int)(allocator, 1, 2, 3);
1658 | auto s2 = s;
1659 | auto s3 = s2.save();
1660 |
1661 | assert(s2.front == 1);
1662 | static assert(!__traits(compiles, s2.front = 4));
1663 | static assert(!__traits(compiles, s2.popFront()));
1664 |
1665 | auto s4 = s2.tail;
1666 | assert(s4.front == 2);
1667 | static assert(!__traits(compiles, s4 = s4.tail));
1668 | }
1669 |
1670 | @safe unittest
1671 | {
1672 | import std.conv;
1673 | import std.experimental.allocator : processAllocator;
1674 | SCAlloc statsCollectorAlloc;
1675 | {
1676 | // TODO: StatsCollector need to be made shareable
1677 | //auto _allocator = sharedAllocatorObject(&statsCollectorAlloc);
1678 | () nothrow pure @safe {
1679 | testConstness(processAllocatorObject());
1680 | testImmutability(processAllocatorObject());
1681 | }();
1682 | }
1683 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1684 | assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1685 | ~ to!string(bytesUsed) ~ " bytes");
1686 | }
1687 |
1688 | version(unittest) private nothrow pure @safe
1689 | void testConcatAndAppend(RCIAllocator allocator)
1690 | {
1691 | import std.algorithm.comparison : equal;
1692 |
1693 | auto dl = DList!(int)(allocator, 1, 2, 3);
1694 | DList!(int) dl2 = DList!int(allocator);
1695 |
1696 | auto dl3 = dl ~ dl2;
1697 | assert(equal(dl3, [1, 2, 3]));
1698 |
1699 | auto dl4 = dl3;
1700 | dl3 = dl3 ~ 4;
1701 | assert(equal(dl3, [1, 2, 3, 4]));
1702 | dl3 = dl3 ~ [5];
1703 | assert(equal(dl3, [1, 2, 3, 4, 5]));
1704 | assert(equal(dl4, [1, 2, 3]));
1705 |
1706 | dl4 = dl3;
1707 | dl3 ~= 6;
1708 | assert(equal(dl3, [1, 2, 3, 4, 5, 6]));
1709 | dl3 ~= [7];
1710 | assert(equal(dl3, [1, 2, 3, 4, 5, 6, 7]));
1711 | assert(equal(dl4, [1, 2, 3, 4, 5, 6, 7]));
1712 |
1713 | dl3 ~= dl3;
1714 | assert(equal(dl3, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]));
1715 | assert(equal(dl4, [1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7]));
1716 |
1717 | DList!int dl5 = DList!int(allocator);
1718 | dl5 ~= [1, 2, 3];
1719 | assert(equal(dl5, [1, 2, 3]));
1720 | }
1721 |
1722 | @safe unittest
1723 | {
1724 | import std.conv;
1725 | SCAlloc statsCollectorAlloc;
1726 | {
1727 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1728 | () nothrow pure @safe {
1729 | testConcatAndAppend(_allocator);
1730 | }();
1731 | }
1732 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1733 | assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1734 | ~ to!string(bytesUsed) ~ " bytes");
1735 | }
1736 |
1737 | version(unittest) private nothrow pure @safe
1738 | void testAssign(RCIAllocator allocator)
1739 | {
1740 | import std.algorithm.comparison : equal;
1741 |
1742 | auto dl = DList!int(allocator, 1, 2, 3);
1743 | assert(equal(dl, [1, 2, 3]));
1744 | {
1745 | auto dl2 = DList!int(allocator, 4, 5, 6);
1746 | dl = dl2;
1747 | assert(equal(dl, dl2));
1748 | }
1749 | assert(equal(dl, [4, 5, 6]));
1750 | dl.popPrev();
1751 | assert(dl.empty);
1752 | }
1753 |
1754 | @safe unittest
1755 | {
1756 | import std.conv;
1757 | SCAlloc statsCollectorAlloc;
1758 | {
1759 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1760 | () nothrow pure @safe {
1761 | testAssign(_allocator);
1762 | }();
1763 | }
1764 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1765 | assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1766 | ~ to!string(bytesUsed) ~ " bytes");
1767 | }
1768 |
1769 | version(unittest) private nothrow pure @safe
1770 | void testWithStruct(RCIAllocator allocator, RCISharedAllocator sharedAlloc)
1771 | {
1772 | import std.algorithm.comparison : equal;
1773 |
1774 | auto list = DList!int(allocator, 1, 2, 3);
1775 | {
1776 | auto listOfLists = DList!(DList!int)(allocator, list);
1777 | size_t pos = 0;
1778 | assert(equal(listOfLists.front, [1, 2, 3]));
1779 | listOfLists.front.front = 2;
1780 | assert(equal(listOfLists.front, [2, 2, 3]));
1781 | static assert(!__traits(compiles, listOfLists.insert(pos, 1)));
1782 |
1783 | auto immListOfLists = immutable DList!(DList!int)(sharedAlloc, list);
1784 | assert(immListOfLists.front.front == 2);
1785 | static assert(!__traits(compiles, immListOfLists.front.front = 2));
1786 | }
1787 | assert(equal(list, [2, 2, 3]));
1788 | }
1789 |
1790 | @safe unittest
1791 | {
1792 | import std.conv;
1793 | import std.experimental.allocator : processAllocator;
1794 | SCAlloc statsCollectorAlloc;
1795 | {
1796 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1797 | () nothrow pure @safe {
1798 | testWithStruct(_allocator, processAllocatorObject());
1799 | }();
1800 | }
1801 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1802 | assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1803 | ~ to!string(bytesUsed) ~ " bytes");
1804 | }
1805 |
1806 | version(unittest) private nothrow pure @safe
1807 | void testWithClass(RCIAllocator allocator)
1808 | {
1809 | class MyClass
1810 | {
1811 | int x;
1812 | this(int x) { this.x = x; }
1813 | }
1814 |
1815 | MyClass c = new MyClass(10);
1816 | {
1817 | auto dl = DList!MyClass(allocator, c);
1818 | assert(dl.front.x == 10);
1819 | assert(dl.front is c);
1820 | dl.front.x = 20;
1821 | }
1822 | assert(c.x == 20);
1823 | }
1824 |
1825 | @safe unittest
1826 | {
1827 | import std.conv;
1828 | SCAlloc statsCollectorAlloc;
1829 | {
1830 | auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))();
1831 | () nothrow pure @safe {
1832 | testWithClass(_allocator);
1833 | }();
1834 | }
1835 | auto bytesUsed = statsCollectorAlloc.bytesUsed;
1836 | assert(bytesUsed == 0, "DList ref count leaks memory; leaked "
1837 | ~ to!string(bytesUsed) ~ " bytes");
1838 | }
1839 |
--------------------------------------------------------------------------------