├── 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 | [![Latest version](https://img.shields.io/dub/v/collections.svg)](http://code.dlang.org/packages/collections) 5 | [![Build Status](https://travis-ci.org/dlang-stdx/collections.svg?branch=master)](https://travis-ci.org/dlang-stdx/collections) 6 | [![Code coverage](https://img.shields.io/codecov/c/github/dlang-stdx/collections.svg?maxAge=86400)](https://codecov.io/gh/dlang-stdx/collections) 7 | [![license](https://img.shields.io/github/license/dlang-stdx/collections.svg)](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 | + '
' 65 | + '
Edit
' 66 | + '
Run
' 67 | + '' 68 | + '
' 69 | + '
' 70 | + '
Application output
Running...
' 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 |
86 | 87 | 88 | 89 | $(SPANID search-query, ) 90 | $(SPANID search-submit, ) 91 |
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 | --------------------------------------------------------------------------------