diff --git a/.dscanner.ini b/.dscanner.ini index 65948fa71fb..12ceca66fac 100644 --- a/.dscanner.ini +++ b/.dscanner.ini @@ -145,7 +145,6 @@ assert_without_msg="-etc.c.SQL_,\ -std.datetime.interval,\ -std.datetime.systime,\ -std.datetime.timezone,\ --std.digest.digest,\ -std.digest.murmurhash,\ -std.digest.sha,\ -std.encoding,\ @@ -208,12 +207,11 @@ assert_without_msg="-etc.c.SQL_,\ -std.utf,\ -std.uuid,\ -std.variant,\ --std.windows.registry,\ --std.xml" +-std.windows.registry" ; Checks for assignment to auto-ref function parameters auto_ref_assignment_check="-std.algorithm.mutation,-std.typecons" ; Checks for variables that could be declared immutable -could_be_immutable_check="-std.algorithm.comparison,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.base64,-std.bigint,-std.bitmanip,-std.checkedint,-std.complex,-std.concurrency,-std.container,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.csv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.stopwatch,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.hmac,-std.digest.md,-std.digest.murmurhash,-std.digest.ripemd,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bitmapped_block,-std.experimental.allocator.building_blocks.bucketizer,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.stats_collector,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.logger.core,-std.logger.multilogger,-std.experimental.typecons,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.cstring,-std.internal.digest.sha_SSSE3,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.scopebuffer,-std.internal.test.dummyrange,-std.json,-std.math,-std.mathspecial,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.interfaces,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.ir,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.regex.internal.thompson,-std.signals,-std.socket,-std.stdio,-std.string,-std.sumtype,-std.traits,-std.typecons,-std.uni,-std.uri,-std.utf,-std.uuid,-std.variant,-std.windows.registry,-std.xml,-std.zip,-std.zlib" +could_be_immutable_check="-std.algorithm.comparison,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.base64,-std.bigint,-std.bitmanip,-std.checkedint,-std.complex,-std.concurrency,-std.container,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.csv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.stopwatch,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.hmac,-std.digest.md,-std.digest.murmurhash,-std.digest.ripemd,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bitmapped_block,-std.experimental.allocator.building_blocks.bucketizer,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.stats_collector,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.logger.core,-std.logger.multilogger,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.cstring,-std.internal.digest.sha_SSSE3,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.scopebuffer,-std.internal.test.dummyrange,-std.json,-std.math,-std.mathspecial,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.interfaces,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.ir,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.regex.internal.thompson,-std.signals,-std.socket,-std.stdio,-std.string,-std.sumtype,-std.traits,-std.typecons,-std.uni,-std.uri,-std.utf,-std.uuid,-std.variant,-std.windows.registry,-std.zip,-std.zlib" ; Check for poor exception handling practices exception_check="-std.concurrency,-std.net.curl,-std.parallelism,-std.range,-std.socket,-std.typecons" ; Checks for poor placement of function attributes @@ -247,7 +245,6 @@ has_public_example="-etc.c.curl,\ -std.logger.core,\ -std.logger.filelogger,\ -std.logger.multilogger,\ --std.experimental.typecons,\ -std.getopt,\ -std.internal.math.biguintcore,\ -std.internal.math.biguintnoasm,\ @@ -271,14 +268,13 @@ has_public_example="-etc.c.curl,\ -std.stdio,\ -std.sumtype,\ -std.uni,\ --std.xml,\ -std.zip,\ -std.zlib" ; Check for sortedness of imports imports_sortedness="+disabled" -;imports_sortedness="-etc.c.curl,-std.algorithm.comparison,-std.algorithm.internal,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.c.freebsd.socket,-std.c.linux.pthread,-std.c.process,-std.complex,-std.concurrency,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.systime,-std.datetime.timezone,-std.digest,-std.digest.hmac,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.common,-std.experimental.allocator.mallocator,-std.experimental.allocator.mmap_allocator,-std.experimental.allocator.showcase,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.logger.core,-std.experimental.typecons,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.math.biguintcore,-std.internal.test.dummyrange,-std.json,-std.math,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.signals,-std.socket,-std.stdio,-std.string,-std.uni,-std.utf,-std.uuid,-std.variant,-std.windows.charset,-std.windows.registry,-std.windows.syserror,-std.zip" +;imports_sortedness="-etc.c.curl,-std.algorithm.comparison,-std.algorithm.internal,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.setops,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.c.freebsd.socket,-std.c.linux.pthread,-std.c.process,-std.complex,-std.concurrency,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.container.util,-std.conv,-std.datetime,-std.datetime.date,-std.datetime.interval,-std.datetime.systime,-std.datetime.timezone,-std.digest,-std.digest.hmac,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.common,-std.experimental.allocator.mallocator,-std.experimental.allocator.mmap_allocator,-std.experimental.allocator.showcase,-std.experimental.allocator.typed,-std.experimental.checkedint,-std.logger.core,-std.file,-std.format,-std.functional,-std.getopt,-std.internal.math.biguintcore,-std.internal.test.dummyrange,-std.json,-std.math,-std.meta,-std.mmfile,-std.net.curl,-std.net.isemail,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.primitives,-std.regex,-std.regex.internal.backtracking,-std.regex.internal.generator,-std.regex.internal.kickstart,-std.regex.internal.parser,-std.regex.internal.tests,-std.signals,-std.socket,-std.stdio,-std.string,-std.uni,-std.utf,-std.uuid,-std.variant,-std.windows.charset,-std.windows.registry,-std.windows.syserror,-std.zip" ; Checks for labels with the same name as variables -label_var_same_name_check="-std.algorithm.iteration,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.conv,-std.encoding,-std.experimental.allocator.building_blocks.segregator,-std.experimental.typecons,-std.internal.digest.sha_SSSE3,-std.parallelism,-std.process,-std.typecons,-std.utf,-std.traits" +label_var_same_name_check="-std.algorithm.iteration,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.conv,-std.encoding,-std.experimental.allocator.building_blocks.segregator,-std.internal.digest.sha_SSSE3,-std.parallelism,-std.process,-std.typecons,-std.utf,-std.traits" ; Checks for subtraction from .length properties length_subtraction_check="+disabled" ;length_subtraction_check="-std.algorithm.internal,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.searching,-std.algorithm.sorting,-std.array,-std.concurrency,-std.container.array,-std.container.binaryheap,-std.conv,-std.datetime.timezone,-std.experimental.allocator.building_blocks.segregator,-std.logger.core,-std.file,-std.format,-std.getopt,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.scopebuffer,-std.math,-std.net.curl,-std.net.isemail,-std.numeric,-std.parallelism,-std.path,-std.process,-std.range,-std.regex,-std.regex.internal.parser,-std.regex.internal.tests,-std.string,-std.uni,-std.windows.charset,-std.windows.registry,-std.zip" @@ -294,7 +290,7 @@ number_style_check="+disabled" ;number_style_check="-std.algorithm.iteration,-std.algorithm.sorting,-std.array,-std.bigint,-std.bitmanip,-std.container.array,-std.conv,-std.datetime.date,-std.datetime.systime,-std.datetime.timezone,-std.digest.crc,-std.digest,-std.digest.md,-std.digest.ripemd,-std.digest.sha,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.checkedint,-std.file,-std.format,-std.functional,-std.internal.math.biguintcore,-std.internal.math.gammafunction,-std.json,-std.math,-std.outbuffer,-std.parallelism,-std.random,-std.range,-std.regex.internal.generator,-std.utf,-std.zip,-std.zlib" ; Checks that opEquals, opCmp, toHash, and toString are either const, immutable ; , or inout. -object_const_check="-std.algorithm.searching,-std.array,-std.bitmanip,-std.checkedint,-std.concurrency,-std.container.rbtree,-std.conv,-std.datetime.interval,-std.encoding,-std.exception,-std.experimental.checkedint,-std.logger.core,-std.experimental.typecons,-std.format,-std.functional,-std.meta,-std.numeric,-std.range,-std.regex,-std.stdio,-std.sumtype,-std.typecons,-std.variant,-std.xml" +object_const_check="-std.algorithm.searching,-std.array,-std.bitmanip,-std.checkedint,-std.concurrency,-std.container.rbtree,-std.conv,-std.datetime.interval,-std.encoding,-std.exception,-std.experimental.checkedint,-std.logger.core,-std.format,-std.functional,-std.meta,-std.numeric,-std.range,-std.regex,-std.stdio,-std.sumtype,-std.typecons,-std.variant" ; Checks that opEquals and toHash are both defined or neither are defined opequals_tohash_check="-std.complex,-std.container.array,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.datetime,-std.datetime.date,-std.experimental.checkedint,-std.internal.test.dummyrange,-std.json,-std.numeric,-std.random,-std.socket,-std.sumtype,-std.typecons,-std.uni" ; Check for properly documented public functions (Returns, Params) @@ -395,16 +391,15 @@ properly_documented_public_functions="-etc.c.odbc.sql,\ -std.utf,\ -std.uuid,\ -std.variant,\ --std.xml,\ -std.zlib" ; Check for redundant attributes redundant_attributes_check="-std.concurrency,-std.digest.md,-std.digest.ripemd,-std.digest.sha,-std.internal.math.biguintcore,-std.math,-std.meta,-std.range,-std.regex.internal.ir,-std.uni,-std.windows.registry" ; Check variable, class, struct, interface, union, and function names against ; the Phobos style guide style_check="+disabled" -;style_check="-etc.c.curl,-etc.c.odbc.sqlext,-etc.c.odbc.sqltypes,-etc.c.odbc.sqlucode,-etc.c.sqlite3,-etc.c.zlib,-std.algorithm.comparison,-std.algorithm.internal,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.sorting,-std.array,-std.base64,-std.bitmanip,-std.c.linux.linux,-std.compiler,-std.container.array,-std.conv,-std.datetime.date,-std.datetime.interval,-std.datetime.systime,-std.digest,-std.digest.murmurhash,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bucketizer,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.null_allocator,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.segregator,-std.experimental.allocator.common,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.allocator.mmap_allocator,-std.experimental.checkedint,-std.experimental.typecons,-std.format,-std.functional,-std.getopt,-std.internal.digest.sha_SSSE3,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.test.dummyrange,-std.internal.unicode_tables,-std.json,-std.math,-std.meta,-std.numeric,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.primitives,-std.regex,-std.regex.internal.ir,-std.regex.internal.kickstart,-std.signals,-std.socket,-std.stdio,-std.string,-std.uni,-std.uri,-std.utf,-std.uuid,-std.variant,-std.zlib" +;style_check="-etc.c.curl,-etc.c.odbc.sqlext,-etc.c.odbc.sqltypes,-etc.c.odbc.sqlucode,-etc.c.sqlite3,-etc.c.zlib,-std.algorithm.comparison,-std.algorithm.internal,-std.algorithm.iteration,-std.algorithm.mutation,-std.algorithm.sorting,-std.array,-std.base64,-std.bitmanip,-std.c.linux.linux,-std.compiler,-std.container.array,-std.conv,-std.datetime.date,-std.datetime.interval,-std.datetime.systime,-std.digest,-std.digest.murmurhash,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bucketizer,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.null_allocator,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.segregator,-std.experimental.allocator.common,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.allocator.mmap_allocator,-std.experimental.checkedint,-std.format,-std.functional,-std.getopt,-std.internal.digest.sha_SSSE3,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.test.dummyrange,-std.internal.unicode_tables,-std.json,-std.math,-std.meta,-std.numeric,-std.parallelism,-std.path,-std.process,-std.random,-std.range,-std.range.primitives,-std.regex,-std.regex.internal.ir,-std.regex.internal.kickstart,-std.signals,-std.socket,-std.stdio,-std.string,-std.uni,-std.uri,-std.utf,-std.uuid,-std.variant,-std.zlib" ; Checks for undocumented public declarations -undocumented_declaration_check="-etc.c.curl,-etc.c.odbc.sql,-etc.c.odbc.sqlext,-etc.c.odbc.sqltypes,-etc.c.odbc.sqlucode,-etc.c.sqlite3,-etc.c.zlib,-std.algorithm.sorting,-std.array,-std.ascii,-std.base64,-std.bitmanip,-std.c.linux.linux,-std.c.linux.socket,-std.c.osx.socket,-std.c.process,-std.compiler,-std.complex,-std.concurrency,-std.container,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.conv,-std.csv,-std.datetime,-std.datetime.date,-std.digest,-std.digest.hmac,-std.digest.md,-std.digest.murmurhash,-std.digest.ripemd,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bitmapped_block,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.quantizer,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.segregator,-std.experimental.allocator.building_blocks.stats_collector,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.checkedint,-std.logger.core,-std.experimental.typecons,-std.file,-std.format,-std.functional,-std.internal.digest.sha_SSSE3,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.test.dummyrange,-std.internal.test.uda,-std.internal.windows.advapi32,-std.json,-std.math,-std.mmfile,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.regex,-std.regex.internal.parser,-std.signals,-std.socket,-std.stdio,-std.string,-std.system,-std.traits,-std.uni,-std.utf,-std.variant,-std.windows.charset,-std.windows.registry,-std.windows.syserror,-std.xml,-std.zip,-std.zlib,-std.int128" +undocumented_declaration_check="-etc.c.curl,-etc.c.odbc.sql,-etc.c.odbc.sqlext,-etc.c.odbc.sqltypes,-etc.c.odbc.sqlucode,-etc.c.sqlite3,-etc.c.zlib,-std.algorithm.sorting,-std.array,-std.ascii,-std.base64,-std.bitmanip,-std.c.linux.linux,-std.c.linux.socket,-std.c.osx.socket,-std.c.process,-std.compiler,-std.complex,-std.concurrency,-std.container,-std.container.array,-std.container.binaryheap,-std.container.dlist,-std.container.rbtree,-std.container.slist,-std.conv,-std.csv,-std.datetime,-std.datetime.date,-std.digest,-std.digest.hmac,-std.digest.md,-std.digest.murmurhash,-std.digest.ripemd,-std.digest.sha,-std.encoding,-std.exception,-std.experimental.allocator,-std.experimental.allocator.building_blocks.affix_allocator,-std.experimental.allocator.building_blocks.allocator_list,-std.experimental.allocator.building_blocks.bitmapped_block,-std.experimental.allocator.building_blocks.fallback_allocator,-std.experimental.allocator.building_blocks.free_list,-std.experimental.allocator.building_blocks.free_tree,-std.experimental.allocator.building_blocks.kernighan_ritchie,-std.experimental.allocator.building_blocks.quantizer,-std.experimental.allocator.building_blocks.region,-std.experimental.allocator.building_blocks.segregator,-std.experimental.allocator.building_blocks.stats_collector,-std.experimental.allocator.gc_allocator,-std.experimental.allocator.mallocator,-std.experimental.checkedint,-std.logger.core,-std.file,-std.format,-std.functional,-std.internal.digest.sha_SSSE3,-std.internal.math.biguintcore,-std.internal.math.biguintnoasm,-std.internal.math.biguintx86,-std.internal.math.errorfunction,-std.internal.math.gammafunction,-std.internal.test.dummyrange,-std.internal.test.uda,-std.internal.windows.advapi32,-std.json,-std.math,-std.mmfile,-std.numeric,-std.outbuffer,-std.parallelism,-std.path,-std.process,-std.regex,-std.regex.internal.parser,-std.signals,-std.socket,-std.stdio,-std.string,-std.system,-std.traits,-std.uni,-std.utf,-std.variant,-std.windows.charset,-std.windows.registry,-std.windows.syserror,-std.zip,-std.zlib,-std.int128" ; Checks for unused labels unused_label_check="-std.conv,-std.internal.math.biguintx86,-std.regex.internal.thompson,-std.signals,-std.uni" ; Checks for unused function parameters @@ -422,7 +417,6 @@ unused_parameter_check="-std.algorithm.comparison,\ -std.encoding,\ -std.experimental.allocator.common,\ -std.experimental.checkedint,\ --std.experimental.typecons,\ -std.format,\ -std.functional,\ -std.getopt,\ @@ -435,8 +429,7 @@ unused_parameter_check="-std.algorithm.comparison,\ -std.regex,\ -std.socket,\ -std.traits,\ --std.typecons,\ --std.xml" +-std.typecons" ; Checks for unused variables unused_variable_check="-std.algorithm.comparison,\ -std.array,\ @@ -475,7 +468,6 @@ unused_variable_check="-std.algorithm.comparison,\ -std.experimental.allocator.typed,\ -std.experimental.checkedint,\ -std.logger.core,\ --std.experimental.typecons,\ -std.file,\ -std.functional,\ -std.getopt,\ @@ -511,11 +503,10 @@ unused_variable_check="-std.algorithm.comparison,\ -std.uuid,\ -std.variant,\ -std.windows.registry,\ --std.xml,\ -std.zip,\ -std.zlib" ; Check for virtual calls in the class constructors -vcall_in_ctor="-std.socket,-std.xml" +vcall_in_ctor="-std.socket" ; Check for @trusted applied to a bigger scope than a single function trust_too_much="-std.regex,-std.stdio,-std.uni,-std.internal.cstring" ; Checks for if statements whose 'then' block is the same as the 'else' block diff --git a/CODEOWNERS b/CODEOWNERS index 8a778208094..4327706ab2b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -36,7 +36,6 @@ std/digest/* @jpf91 std/experimental/allocator/* @andralex @wilzbach @PetarKirov std/experimental/checkedint/* @andralex std/experimental/logger/* @burner -# std/experimental/typecons.d std/file.d @CyberShadow # std/format.d # std/functional.d @@ -72,6 +71,5 @@ std/utf.d @jmdavis std/uuid.d @jpf91 # std/variant.d std/windows/* @CyberShadow -# std/xml.d std/zip.d @CyberShadow std/zlib.d @CyberShadow diff --git a/changelog/borrow_for_refcounted.dd b/changelog/borrow_for_refcounted.dd new file mode 100644 index 00000000000..be55d2f8cfc --- /dev/null +++ b/changelog/borrow_for_refcounted.dd @@ -0,0 +1,52 @@ +Added `SafeRefCounted`, that can be used in `@safe` with `-preview=dip1000`. + +`RefCounted` is only available for `@system` code, because of the possibility of +escaping a reference to its payload past the end of its lifetime. a modified +copy of it, `std.typecons.SafeRefCounted` has been added. Also added is a +`borrow` function, that lets one safely access and modify the payload. +`-preview=dip1000` prevents escaping a reference to it in `@safe` code. + +------- +@safe pure nothrow void fun() +{ + import std.typecons; + + auto rcInt = safeRefCounted(5); + assert(rcInt.borrow!(theInt => theInt) == 5); + auto sameInt = rcInt; + assert(sameInt.borrow!"a" == 5); + + // using `ref` in the function + auto arr = [0, 1, 2, 3, 4, 5, 6]; + sameInt.borrow!(ref (x) => arr[x]) = 10; + assert(arr == [0, 1, 2, 3, 4, 10, 6]); + + // modifying the payload via an alias + sameInt.borrow!"a*=2"; + assert(rcInt.borrow!"a" == 10); +} +------- + +Direct access to the payload unfortunately has to be `@system`, though. While +`-dip1000` could prevent escaping the reference, it is possible to destroy the +last reference before the end of it's scope: + +------- +int destroyFirstAndUseLater() +{ + import std.typecons; + + auto rc = SafeRefCounted!int(123); + int* ptr = &rc.refCountedPayload(); + destroy(rc); + return *ptr; // Reads from freed memory. Don't do this. +} +------- + +As a side effect, this enabled us to make $(REF dirEntries, std, file) `@safe` +with `-preview=dip1000`. + +Some member functions of `RefCounted` that are `@safe` are not so in +`SafeRefCounted`. The `RefCounted` type and `refCounted` function are still +available for the old behaviour. However, their main purpose is backwards +compatibility. They are not recommended for new code. diff --git a/changelog/remove-experimental-typecons.dd b/changelog/remove-experimental-typecons.dd new file mode 100644 index 00000000000..4979dc2994f --- /dev/null +++ b/changelog/remove-experimental-typecons.dd @@ -0,0 +1,4 @@ +std.experimental.typecons has been removed + +This was an attempt to update `std.typecons.wrap` with an implementation that could work with struct, but it did not go anywhere. +See [this post on the forum](https://forum.dlang.org/post/ialskabybaxtqamgtwge@forum.dlang.org). diff --git a/changelog/remove-std-digest-digest.dd b/changelog/remove-std-digest-digest.dd new file mode 100644 index 00000000000..08af29bad2d --- /dev/null +++ b/changelog/remove-std-digest-digest.dd @@ -0,0 +1,5 @@ +std.digest.digest has been removed + +This module was initially deprecated in 2.076.1, and has been empty since +2.092.0 when all deprecated symbols were removed in favour of importing +`std.digest` or its submodules instead. diff --git a/changelog/remove-std-xml.dd b/changelog/remove-std-xml.dd new file mode 100644 index 00000000000..ef491cb6e9b --- /dev/null +++ b/changelog/remove-std-xml.dd @@ -0,0 +1,4 @@ +std.xml has been removed + +This module is considered out-dated and not up to Phobos' current standards. +If you still need it, go to $(LINK https://github.com/DigitalMars/undeaD) diff --git a/index.dd b/index.dd index 45c248e1178..48122259670 100644 --- a/index.dd +++ b/index.dd @@ -70,10 +70,6 @@ $(BOOKTABLE , $(TDNW $(MREF std,json)) $(TD Read/write data in JSON format.) ) - $(TR - $(TDNW $(MREF std,xml)) - $(TD Read/write data in XML format.) - ) $(TR $(TDNW $(MREF std,zip)) $(TD Read/write data in the ZIP archive format.) diff --git a/posix.mak b/posix.mak index 1e6a3613c80..2ef5d459aea 100644 --- a/posix.mak +++ b/posix.mak @@ -216,8 +216,8 @@ PACKAGE_std = array ascii base64 bigint bitmanip checkedint compiler complex con functional getopt int128 json mathspecial meta mmfile numeric \ outbuffer package parallelism path process random signals socket stdint \ stdio string sumtype system traits typecons \ - uri utf uuid variant xml zip zlib -PACKAGE_std_experimental = checkedint typecons + uri utf uuid variant zip zlib +PACKAGE_std_experimental = checkedint PACKAGE_std_algorithm = comparison iteration mutation package searching setops \ sorting PACKAGE_std_container = array binaryheap dlist package rbtree slist util @@ -259,7 +259,6 @@ EXTRA_DOCUMENTABLES := $(EXTRA_MODULES_COMMON) EXTRA_MODULES_INTERNAL := $(addprefix std/, \ algorithm/internal \ - digest/digest \ $(addprefix internal/, \ cstring memory digest/sha_SSSE3 \ $(addprefix math/, biguintcore biguintnoasm biguintx86 \ diff --git a/std/algorithm/comparison.d b/std/algorithm/comparison.d index b810fbb9258..5ecb4f601a5 100644 --- a/std/algorithm/comparison.d +++ b/std/algorithm/comparison.d @@ -577,14 +577,24 @@ Returns: and `T3` are different. */ T1 clamp(T1, T2, T3)(T1 val, T2 lower, T3 upper) -if (is(typeof(val.lessThan(lower) ? lower : val.greaterThan(upper) ? upper : val) : T1)) +if (is(typeof(val.lessThan(lower) ? lower : val.greaterThan(upper) ? upper : val)) + && (is(T2 : T1) && is(T3 : T1))) +// cannot use : +// `if (is(typeof(val.lessThan(lower) ? lower : val.greaterThan(upper) ? upper : val) : T1)) +// because of https://issues.dlang.org/show_bug.cgi?id=16235. +// Once that is fixed, we can simply use the ternary in both the template constraint +// and the template body in { assert(!lower.greaterThan(upper), "Lower can't be greater than upper."); } do { - return val.lessThan(lower) ? lower : val.greaterThan(upper) ? upper : val; + if (val.lessThan(lower)) + return lower; + else if (val.greaterThan(upper)) + return upper; + return val; } /// @@ -637,6 +647,12 @@ do assert(x.clamp(lo, hi).y == 42); } +// https://issues.dlang.org/show_bug.cgi?id=23268 +@safe pure nothrow @nogc unittest +{ + static assert(__traits(compiles, clamp(short.init, short.init, cast(const) short.init))); +} + // cmp /********************************** Performs a lexicographical comparison on two diff --git a/std/algorithm/iteration.d b/std/algorithm/iteration.d index 300a8978fe5..9a365d59e2c 100644 --- a/std/algorithm/iteration.d +++ b/std/algorithm/iteration.d @@ -771,6 +771,23 @@ private struct MapResult(alias fun, Range) assert(dd.length == 4); } +// Verify fix for: https://issues.dlang.org/show_bug.cgi?id=16034 +@safe unittest +{ + struct One + { + int entry = 1; + @disable this(this); + } + + One[] ones = [One(), One()]; + + import std.algorithm.comparison : equal; + + assert(ones.map!`a.entry + 1`.equal([2, 2])); +} + + @safe unittest { import std.algorithm.comparison : equal; @@ -1798,7 +1815,7 @@ if (isInputRange!R) assert(equal(g3, [ tuple(1, 2u), tuple(2, 2u) ])); interface I {} - class C : I { override size_t toHash() const nothrow @safe { return 0; } } + static class C : I { override size_t toHash() const nothrow @safe { return 0; } } const C[] a4 = [new const C()]; auto g4 = a4.group!"a is b"; assert(g4.front[1] == 1); @@ -2255,25 +2272,26 @@ if (isForwardRange!Range) import std.algorithm.comparison : equal; size_t popCount = 0; - class RefFwdRange + static class RefFwdRange { int[] impl; + size_t* pcount; @safe nothrow: - this(int[] data) { impl = data; } + this(int[] data, size_t* pcount) { impl = data; this.pcount = pcount; } @property bool empty() { return impl.empty; } @property auto ref front() { return impl.front; } void popFront() { impl.popFront(); - popCount++; + (*pcount)++; } - @property auto save() { return new RefFwdRange(impl); } + @property auto save() { return new RefFwdRange(impl, pcount); } } static assert(isForwardRange!RefFwdRange); - auto testdata = new RefFwdRange([1, 3, 5, 2, 4, 7, 6, 8, 9]); + auto testdata = new RefFwdRange([1, 3, 5, 2, 4, 7, 6, 8, 9], &popCount); auto groups = testdata.chunkBy!((a,b) => (a % 2) == (b % 2)); auto outerSave1 = groups.save; @@ -6058,7 +6076,7 @@ if (is(typeof(binaryFun!pred(r.front, s.front)) : bool) import std.algorithm.comparison : equal; // Test by-reference separator - class RefSep { + static class RefSep { @safe: string _impl; this(string s) { _impl = s; } @@ -7908,7 +7926,13 @@ if (isRandomAccessRange!Range && hasLength!Range) _indices = iota(size_t(r.length)).array; _empty = r.length == 0; } - + private this(size_t[] indices, size_t[] state, Range r, bool empty_) + { + _indices = indices; + _state = state; + _r = r; + _empty = empty_; + } /// Returns: `true` if the range is empty, `false` otherwise. @property bool empty() const pure nothrow @safe @nogc { @@ -7949,6 +7973,11 @@ if (isRandomAccessRange!Range && hasLength!Range) next(2); } + /// Returns: an independent copy of the permutations range. + auto save() + { + return typeof(this)(_indices.dup, _state.dup, _r.save, _empty); + } } /// @@ -7964,3 +7993,15 @@ if (isRandomAccessRange!Range && hasLength!Range) [1, 2, 0], [2, 1, 0]])); } + +@safe unittest +{ + import std.algorithm.comparison : equal; + import std.range : ElementType; + import std.array : array; + auto p = [1, 2, 3].permutations; + auto x = p.save.front; + p.popFront; + auto y = p.front; + assert(x != y); +} diff --git a/std/algorithm/searching.d b/std/algorithm/searching.d index 870b1b40219..15f7ca99e66 100644 --- a/std/algorithm/searching.d +++ b/std/algorithm/searching.d @@ -5002,7 +5002,7 @@ If set to `OpenRight.yes`, then the interval is open to the right (last element is not included). Otherwise if set to `OpenRight.no`, then the interval is closed to the right -(last element included). +including the entire sentinel. */ alias OpenRight = Flag!"openRight"; @@ -5052,6 +5052,7 @@ if (isInputRange!Range) static if (!is(Sentinel == void)) private Sentinel _sentinel; private OpenRight _openRight; + private bool _matchStarted; private bool _done; static if (!is(Sentinel == void)) @@ -5063,7 +5064,19 @@ if (isInputRange!Range) _input = input; _sentinel = sentinel; _openRight = openRight; - _done = _input.empty || openRight && predSatisfied(); + static if (isInputRange!Sentinel) + { + _matchStarted = predSatisfied(); + _done = _input.empty || _sentinel.empty || openRight && _matchStarted; + if (_matchStarted && !_done && !openRight) + { + _sentinel.popFront; + } + } + else + { + _done = _input.empty || openRight && predSatisfied(); + } } private this(Range input, Sentinel sentinel, OpenRight openRight, bool done) @@ -5118,9 +5131,32 @@ if (isInputRange!Range) assert(!empty, "Can not popFront of an empty Until"); if (!_openRight) { - _done = predSatisfied(); - _input.popFront(); - _done = _done || _input.empty; + static if (isInputRange!Sentinel) + { + _input.popFront(); + _done = _input.empty || _sentinel.empty; + if (!_done) + { + if (_matchStarted) + { + _sentinel.popFront; + } + else + { + _matchStarted = predSatisfied(); + if (_matchStarted) + { + _sentinel.popFront; + } + } + } + } + else + { + _done = predSatisfied(); + _input.popFront(); + _done = _done || _input.empty; + } } else { @@ -5212,3 +5248,33 @@ pure @safe unittest assert(equal(r.save, "foo")); } } +// https://issues.dlang.org/show_bug.cgi?id=14543 +pure @safe unittest +{ + import std.algorithm.comparison : equal; + import std.uni : toUpper; + assert("one two three".until("two").equal("one ")); + assert("one two three".until("two", OpenRight.no).equal("one two")); + + assert("one two three".until("two", No.openRight).equal("one two")); + assert("one two three".until("two", Yes.openRight).equal("one ")); + + assert("one two three".until('t', Yes.openRight).equal("one ")); + assert("one two three".until("", Yes.openRight).equal("")); + assert("one two three".until("", No.openRight).equal("")); + + assert("one two three".until("three", No.openRight).equal("one two three")); + assert("one two three".until("three", Yes.openRight).equal("one two ")); + + assert("one two three".until("one", No.openRight).equal("one")); + assert("one two three".until("one", Yes.openRight).equal("")); + + assert("one two three".until("o", No.openRight).equal("o")); + assert("one two three".until("o", Yes.openRight).equal("")); + + assert("one two three".until("", No.openRight).equal("")); + assert("one two three".until("", Yes.openRight).equal("")); + + assert("one two three".until!((a,b)=>a.toUpper == b)("TWO", No.openRight).equal("one two")); +} + diff --git a/std/algorithm/sorting.d b/std/algorithm/sorting.d index ee68b234b15..4fc7ee9ac4c 100644 --- a/std/algorithm/sorting.d +++ b/std/algorithm/sorting.d @@ -1642,9 +1642,9 @@ private void multiSortImpl(Range, SwapStrategy ss, funs...)(Range r) } // https://issues.dlang.org/show_bug.cgi?id=16413 - @system comparison function -@safe unittest +@system unittest { - bool lt(int a, int b) { return a < b; } static @system + static @system bool lt(int a, int b) { return a < b; } auto a = [2, 1]; a.multiSort!(lt, lt); assert(a == [1, 2]); diff --git a/std/base64.d b/std/base64.d index d971dba1c71..2f7213b737b 100644 --- a/std/base64.d +++ b/std/base64.d @@ -922,7 +922,7 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') * length $(D_PARAM sourceLength). */ @safe - pure nothrow size_t decodeLength(in size_t sourceLength) + pure @nogc nothrow size_t decodeLength(in size_t sourceLength) { static if (Padding == NoPadding) return (sourceLength / 4) * 3 + (sourceLength % 4 < 2 ? 0 : sourceLength % 4 == 2 ? 1 : 2); @@ -946,7 +946,7 @@ template Base64Impl(char Map62th, char Map63th, char Padding = '=') // Used in decode contracts. Calculates the actual size the decoded // result should have, taking into account trailing padding. @safe - pure nothrow private size_t realDecodeLength(R)(R source) + pure @nogc nothrow private size_t realDecodeLength(R)(R source) { auto expect = decodeLength(source.length); static if (Padding != NoPadding) diff --git a/std/compiler.d b/std/compiler.d index 2f983c58fe0..4ea5bd72433 100644 --- a/std/compiler.d +++ b/std/compiler.d @@ -28,7 +28,7 @@ immutable gnu = 2, /// GNU D Compiler (GDC) llvm = 3, /// LLVM D Compiler (LDC) dotNET = 4, /// D.NET - sdc = 5, /// Stupid D Compiler (SDC) + sdc = 5, /// Snazzy D Compiler (SDC) } /// Which vendor produced this compiler. diff --git a/std/concurrency.d b/std/concurrency.d index fb383ae3f2e..bc53236b9b7 100644 --- a/std/concurrency.d +++ b/std/concurrency.d @@ -257,9 +257,12 @@ private @property ref ThreadInfo thisInfo() nothrow { - if (scheduler is null) + import core.atomic : atomicLoad; + + auto localScheduler = atomicLoad(scheduler); + if (localScheduler is null) return ThreadInfo.thisInfo; - return scheduler.thisInfo; + return localScheduler.thisInfo; } } diff --git a/std/container/array.d b/std/container/array.d index 08f9ead196e..ecc45996925 100644 --- a/std/container/array.d +++ b/std/container/array.d @@ -412,9 +412,9 @@ if (!is(immutable T == immutable bool)) .destroy(e); static if (hasIndirections!T) - GC.removeRange(_payload.ptr); + GC.removeRange(cast(void*) _payload.ptr); - free(_payload.ptr); + free(cast(void*) _payload.ptr); } this(this) @disable; @@ -489,14 +489,14 @@ if (!is(immutable T == immutable bool)) auto newPayload = newPayloadPtr[0 .. oldLength]; // copy old data over to new array - memcpy(newPayload.ptr, _payload.ptr, T.sizeof * oldLength); + memcpy(cast(void*) newPayload.ptr, cast(void*) _payload.ptr, T.sizeof * oldLength); // Zero out unused capacity to prevent gc from seeing false pointers - memset(newPayload.ptr + oldLength, + memset( cast(void*) (newPayload.ptr + oldLength), 0, (elements - oldLength) * T.sizeof); - GC.addRange(newPayload.ptr, sz); - GC.removeRange(_payload.ptr); - free(_payload.ptr); + GC.addRange(cast(void*) newPayload.ptr, sz); + GC.removeRange(cast(void*) _payload.ptr); + free(cast(void*) _payload.ptr); _payload = newPayload; } else @@ -611,12 +611,17 @@ if (!is(immutable T == immutable bool)) return opEquals(rhs); } + // fix https://issues.dlang.org/show_bug.cgi?23140 + private alias Unshared(T) = T; + private alias Unshared(T: shared U, U) = U; + /// ditto bool opEquals(ref const Array rhs) const { if (empty) return rhs.empty; if (rhs.empty) return false; - return _data._payload == rhs._data._payload; + + return cast(Unshared!(T)[]) _data._payload == cast(Unshared!(T)[]) rhs._data._payload; } /** @@ -1740,6 +1745,16 @@ if (!is(immutable T == immutable bool)) assertThrown!AssertError(array.length = 5); } +// https://issues.dlang.org/show_bug.cgi?id=23140 +@system unittest +{ + shared class C + { + } + + Array!C ac; + ac = Array!C([new C]); +} //////////////////////////////////////////////////////////////////////////////// // Array!bool //////////////////////////////////////////////////////////////////////////////// diff --git a/std/container/binaryheap.d b/std/container/binaryheap.d index e763357a012..723630c89e2 100644 --- a/std/container/binaryheap.d +++ b/std/container/binaryheap.d @@ -89,6 +89,8 @@ if (isRandomAccessRange!(Store) || isRandomAccessRange!(typeof(Store.init[]))) Store _store; size_t _length; } + // TODO: migrate to use the SafeRefCounted. The problem is that some member + // functions here become @system with a naive switch. private RefCounted!(Data, RefCountedAutoInitialize.no) _payload; // Comparison predicate private alias comp = binaryFun!(less); diff --git a/std/container/dlist.d b/std/container/dlist.d index 32d56ecc733..11e3883e5ee 100644 --- a/std/container/dlist.d +++ b/std/container/dlist.d @@ -111,7 +111,7 @@ private struct DRange static assert(is(ElementType!DRange == BaseNode*)); } -nothrow @safe pure: +nothrow @safe @nogc pure: private BaseNode* _first; private BaseNode* _last; diff --git a/std/container/rbtree.d b/std/container/rbtree.d index 622dee4df2f..9bd8d27c065 100644 --- a/std/container/rbtree.d +++ b/std/container/rbtree.d @@ -2055,7 +2055,7 @@ if ( is(typeof(binaryFun!less((ElementType!Stuff).init, (ElementType!Stuff).init } //Combinations not in examples. -@safe pure unittest +@system pure unittest { auto rbt1 = redBlackTree!(true, string)("hello", "hello"); auto rbt2 = redBlackTree!((a, b){return a < b;}, double)(5.1, 2.3); diff --git a/std/conv.d b/std/conv.d index 9164e079865..0f660658920 100644 --- a/std/conv.d +++ b/std/conv.d @@ -4894,7 +4894,7 @@ if (isOctalLiteral(num)) template octal(alias decimalInteger) if (is(typeof(decimalInteger)) && isIntegral!(typeof(decimalInteger))) { - enum octal = octal!(typeof(decimalInteger))(to!string(decimalInteger)); + enum octal = convertToOctal(decimalInteger); } /// @@ -4910,6 +4910,19 @@ if (is(typeof(decimalInteger)) && isIntegral!(typeof(decimalInteger))) auto d = octal!"0001_200_000"; } +/************************************* + * Convert a decimal integer to an octal integer with the same digits. + * Params: + * i = integer to convert + * Returns: + * octal integer with the same type and same digits + */ +private T convertToOctal(T)(T i) +{ + assert((i % 10) < 8); + return i ? convertToOctal(i / 10) * 8 + i % 10 : 0; +} + /* Takes a string, num, which is an octal literal, and returns its value, in the type T specified. diff --git a/std/datetime/stopwatch.d b/std/datetime/stopwatch.d index d8ecf7dbfbb..eedc0ea79c9 100644 --- a/std/datetime/stopwatch.d +++ b/std/datetime/stopwatch.d @@ -234,7 +234,7 @@ public: /++ - Peek at the amount of time that the the StopWatch has been running. + Peek at the amount of time that the StopWatch has been running. This does not include any time during which the StopWatch was stopped but does include $(I all) of the time that it was running and not just the diff --git a/std/datetime/timezone.d b/std/datetime/timezone.d index 28255f4ef99..b23891808f5 100644 --- a/std/datetime/timezone.d +++ b/std/datetime/timezone.d @@ -3397,8 +3397,7 @@ struct TZConversions TZConversions parseTZConversions(string windowsZonesXMLText) @safe pure { // This is a bit hacky, since it doesn't properly read XML, but it avoids - // needing to pull in std.xml (which we're theoretically replacing at some - // point anyway). + // needing to pull in an xml parsing module. import std.algorithm.iteration : uniq; import std.algorithm.searching : find; import std.algorithm.sorting : sort; diff --git a/std/digest/crc.d b/std/digest/crc.d index 38563b14cd0..b7922bb03da 100644 --- a/std/digest/crc.d +++ b/std/digest/crc.d @@ -555,7 +555,7 @@ ubyte[8] crc64ECMAOf(T...)(T data) } /** - * This is a convenience alias for $(REF digest, std,digest,digest) using the + * This is a convenience alias for $(REF digest, std,digest) using the * CRC64-ISO implementation. * * Params: @@ -611,7 +611,7 @@ alias CRC32Digest = WrapperDigest!CRC32; * OOP API CRC64-ECMA implementation. * See `std.digest` for differences between template and OOP API. * - * This is an alias for $(D $(REF WrapperDigest, std,digest,digest)!CRC64ECMA), + * This is an alias for $(D $(REF WrapperDigest, std,digest)!CRC64ECMA), * see there for more information. */ alias CRC64ECMADigest = WrapperDigest!CRC64ECMA; @@ -620,7 +620,7 @@ alias CRC64ECMADigest = WrapperDigest!CRC64ECMA; * OOP API CRC64-ISO implementation. * See `std.digest` for differences between template and OOP API. * - * This is an alias for $(D $(REF WrapperDigest, std,digest,digest)!CRC64ISO), + * This is an alias for $(D $(REF WrapperDigest, std,digest)!CRC64ISO), * see there for more information. */ alias CRC64ISODigest = WrapperDigest!CRC64ISO; @@ -628,7 +628,7 @@ alias CRC64ISODigest = WrapperDigest!CRC64ISO; /// @safe unittest { - //Simple example, hashing a string using Digest.digest helper function + //Simple example, hashing a string using CRC32Digest.digest helper function auto crc = new CRC32Digest(); ubyte[] hash = crc.digest("abc"); //Let's get a hash string diff --git a/std/digest/digest.d b/std/digest/digest.d deleted file mode 100644 index 01fdbd75e4b..00000000000 --- a/std/digest/digest.d +++ /dev/null @@ -1,3 +0,0 @@ -// @@@DEPRECATED_2.101@@@ -deprecated("import std.digest instead of std.digest.digest. std.digest.digest will be removed in 2.101") -module std.digest.digest; diff --git a/std/experimental/allocator/building_blocks/affix_allocator.d b/std/experimental/allocator/building_blocks/affix_allocator.d index 1e3df91f95e..d0d0b7cb27c 100644 --- a/std/experimental/allocator/building_blocks/affix_allocator.d +++ b/std/experimental/allocator/building_blocks/affix_allocator.d @@ -521,9 +521,9 @@ version (StdUnittest) @system unittest { - import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; - auto a = AffixAllocator!(Region!(), uint)(Region!()(new ubyte[1024 * 64])); + auto a = AffixAllocator!(BorrowedRegion!(), uint)(BorrowedRegion!()(new ubyte[1024 * 64])); auto b = a.allocate(42); assert(b.length == 42); // Test that expand infers from parent diff --git a/std/experimental/allocator/building_blocks/aligned_block_list.d b/std/experimental/allocator/building_blocks/aligned_block_list.d index 14c6de4696f..99768bc318e 100644 --- a/std/experimental/allocator/building_blocks/aligned_block_list.d +++ b/std/experimental/allocator/building_blocks/aligned_block_list.d @@ -526,7 +526,7 @@ shared struct SharedAlignedBlockList(Allocator, ParentAllocator, ulong theAlignm /// @system unittest { - import std.experimental.allocator.building_blocks.region : SharedRegion; + import std.experimental.allocator.building_blocks.region : SharedBorrowedRegion; import std.experimental.allocator.building_blocks.ascending_page_allocator : SharedAscendingPageAllocator; import std.experimental.allocator.building_blocks.null_allocator : NullAllocator; import core.thread : ThreadGroup; @@ -536,11 +536,11 @@ shared struct SharedAlignedBlockList(Allocator, ParentAllocator, ulong theAlignm enum maxIter = 10; /* - In this example we use 'SharedAlignedBlockList' together with 'SharedRegion', - in order to create a fast, thread-safe allocator. + In this example we use 'SharedAlignedBlockList' together with + 'SharedBorrowedRegion', in order to create a fast, thread-safe allocator. */ alias SuperAllocator = SharedAlignedBlockList!( - SharedRegion!(NullAllocator, 1), + SharedBorrowedRegion!(1), SharedAscendingPageAllocator, 4096); @@ -597,7 +597,7 @@ version (StdUnittest) SpinLock lock = SpinLock(SpinLock.Contention.brief); alias SuperAllocator = SharedAlignedBlockList!( - SharedRegion!(NullAllocator, 1), + SharedBorrowedRegion!(1), SharedAscendingPageAllocator, 1 << 16); void[][totalAllocs] buf; diff --git a/std/experimental/allocator/building_blocks/allocator_list.d b/std/experimental/allocator/building_blocks/allocator_list.d index bcab16d6d8f..ca83785914e 100644 --- a/std/experimental/allocator/building_blocks/allocator_list.d +++ b/std/experimental/allocator/building_blocks/allocator_list.d @@ -679,8 +679,8 @@ version (Posix) @system unittest { // Create an allocator based upon 4MB regions, fetched from the GC heap. import std.algorithm.comparison : max; - import std.experimental.allocator.building_blocks.region : Region; - AllocatorList!((n) => Region!()(new ubyte[max(n, 1024 * 4096)])) a; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; + AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a; auto b1 = a.alignedAllocate(1024 * 8192, 1024); assert(b1 !is null); // still works due to overdimensioning assert(b1.length == 1024 * 8192); @@ -707,8 +707,8 @@ version (Posix) @system unittest // Create an allocator based upon 4MB regions, fetched from the GC heap. import std.algorithm.comparison : max; - import std.experimental.allocator.building_blocks.region : Region; - AllocatorList!((n) => Region!()(new ubyte[max(n, 1024 * 4096)])) a; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; + AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a; auto b1 = a.alignedAllocate(0, 1); assert(b1 is null); @@ -728,8 +728,8 @@ version (Posix) @system unittest // Create an allocator based upon 4MB regions, fetched from the GC heap. import std.algorithm.comparison : max; - import std.experimental.allocator.building_blocks.region : Region; - AllocatorList!((n) => Region!()(new ubyte[max(n, 1024 * 4096)])) a; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; + AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a; auto b0 = a.alignedAllocate(1, 1024); assert(b0.length == 1); assert(b0.ptr.alignedAt(1024)); @@ -765,8 +765,8 @@ version (Posix) @system unittest { // Create an allocator based upon 4MB regions, fetched from the GC heap. import std.algorithm.comparison : max; - import std.experimental.allocator.building_blocks.region : Region; - AllocatorList!((n) => Region!()(new ubyte[max(n, 1024 * 4096)])) a; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; + AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)])) a; auto b1 = a.allocate(1024 * 8192); assert(b1 !is null); // still works due to overdimensioning b1 = a.allocate(1024 * 10); @@ -779,10 +779,10 @@ version (Posix) @system unittest @system unittest { import std.algorithm.comparison : max; - import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; import std.experimental.allocator.mallocator : Mallocator; import std.typecons : Ternary; - AllocatorList!((n) => Region!()(new ubyte[max(n, 1024 * 4096)]), Mallocator) a; + AllocatorList!((n) => BorrowedRegion!()(new ubyte[max(n, 1024 * 4096)]), Mallocator) a; auto b1 = a.allocate(1024 * 8192); assert(b1 !is null); b1 = a.allocate(1024 * 10); diff --git a/std/experimental/allocator/building_blocks/fallback_allocator.d b/std/experimental/allocator/building_blocks/fallback_allocator.d index b413d738dab..3990418c3cf 100644 --- a/std/experimental/allocator/building_blocks/fallback_allocator.d +++ b/std/experimental/allocator/building_blocks/fallback_allocator.d @@ -342,12 +342,12 @@ struct FallbackAllocator(Primary, Fallback) @system unittest { - import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; import std.typecons : Ternary; - auto a = FallbackAllocator!(Region!(), Region!())( - Region!()(new ubyte[4096 * 1024]), - Region!()(new ubyte[4096 * 1024])); + auto a = FallbackAllocator!(BorrowedRegion!(), BorrowedRegion!())( + BorrowedRegion!()(new ubyte[4096 * 1024]), + BorrowedRegion!()(new ubyte[4096 * 1024])); auto b = a.alignedAllocate(42, 8); assert(b.length == 42); @@ -506,11 +506,11 @@ version (StdUnittest) @system unittest { - import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; import std.typecons : Ternary; - alias A = FallbackAllocator!(Region!(), Region!()); - auto a = A(Region!()(new ubyte[16_384]), Region!()(new ubyte[16_384])); + alias A = FallbackAllocator!(BorrowedRegion!(), BorrowedRegion!()); + auto a = A(BorrowedRegion!()(new ubyte[16_384]), BorrowedRegion!()(new ubyte[16_384])); auto b = a.allocate(42); assert(b.length == 42); diff --git a/std/experimental/allocator/building_blocks/free_list.d b/std/experimental/allocator/building_blocks/free_list.d index 7055d666fb1..d2b32099226 100644 --- a/std/experimental/allocator/building_blocks/free_list.d +++ b/std/experimental/allocator/building_blocks/free_list.d @@ -486,9 +486,9 @@ struct FreeList(ParentAllocator, // Test that deallocateAll infers from parent @system unittest { - import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; - auto fl = FreeList!(Region!(), 0, 16)(Region!()(new ubyte[1024 * 64])); + auto fl = FreeList!(BorrowedRegion!(), 0, 16)(BorrowedRegion!()(new ubyte[1024 * 64])); auto b = fl.allocate(42); assert(b.length == 42); assert((() pure nothrow @safe @nogc => fl.expand(b, 48))()); diff --git a/std/experimental/allocator/building_blocks/free_tree.d b/std/experimental/allocator/building_blocks/free_tree.d index bd4bb9511ea..fe59e2650a1 100644 --- a/std/experimental/allocator/building_blocks/free_tree.d +++ b/std/experimental/allocator/building_blocks/free_tree.d @@ -502,9 +502,9 @@ version (StdUnittest) @system unittest { - import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; - auto a = FreeTree!(Region!())(Region!()(new ubyte[1024 * 64])); + auto a = FreeTree!(BorrowedRegion!())(BorrowedRegion!()(new ubyte[1024 * 64])); auto b = a.allocate(42); assert(b.length == 42); assert((() pure nothrow @safe @nogc => a.expand(b, 22))()); diff --git a/std/experimental/allocator/building_blocks/quantizer.d b/std/experimental/allocator/building_blocks/quantizer.d index 762b3797b41..3334a86fd84 100644 --- a/std/experimental/allocator/building_blocks/quantizer.d +++ b/std/experimental/allocator/building_blocks/quantizer.d @@ -315,14 +315,14 @@ version (StdUnittest) version (StdUnittest) @system unittest { - import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; import std.typecons : Ternary; - alias MyAlloc = Quantizer!(Region!(), + alias MyAlloc = Quantizer!(BorrowedRegion!(), (size_t n) => n.roundUpToMultipleOf(64)); - testAllocator!(() => MyAlloc(Region!()(new ubyte[1024 * 64]))); + testAllocator!(() => MyAlloc(BorrowedRegion!()(new ubyte[1024 * 64]))); - auto a = MyAlloc(Region!()(new ubyte[1024 * 64])); + auto a = MyAlloc(BorrowedRegion!()(new ubyte[1024 * 64])); // Check that empty inherits from parent assert((() pure nothrow @safe @nogc => a.empty)() == Ternary.yes); auto b = a.allocate(42); diff --git a/std/experimental/allocator/building_blocks/region.d b/std/experimental/allocator/building_blocks/region.d index be0d274633c..8c397849f43 100644 --- a/std/experimental/allocator/building_blocks/region.d +++ b/std/experimental/allocator/building_blocks/region.d @@ -29,8 +29,7 @@ the store and the limits. One allocation entails rounding up the allocation size for alignment purposes, bumping the current pointer, and comparing it against the limit. -If `ParentAllocator` is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator), `Region` -deallocates the chunk of memory during destruction. +`Region` deallocates the chunk of memory during destruction. The `minAlign` parameter establishes alignment. If $(D minAlign > 1), the sizes of all allocation requests are rounded up to a multiple of `minAlign`. @@ -38,7 +37,7 @@ Applications aiming at maximum speed may want to choose $(D minAlign = 1) and control alignment externally. */ -struct Region(ParentAllocator = NullAllocator, +struct Region(ParentAllocator, uint minAlign = platformAlignment, Flag!"growDownwards" growDownwards = No.growDownwards) { @@ -63,50 +62,42 @@ struct Region(ParentAllocator = NullAllocator, alias parent = ParentAllocator.instance; } - private void* _current, _begin, _end; + private BorrowedRegion!(minAlign, growDownwards) _impl; private void* roundedBegin() const pure nothrow @trusted @nogc { - return cast(void*) roundUpToAlignment(cast(size_t) _begin, alignment); + return _impl.roundedBegin; } private void* roundedEnd() const pure nothrow @trusted @nogc { - return cast(void*) roundDownToAlignment(cast(size_t) _end, alignment); + return _impl.roundedEnd; } /** Constructs a region backed by a user-provided store. - Assumes the memory was allocated with `ParentAllocator` - (if different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator)). + Assumes the memory was allocated with `ParentAllocator`. Params: - store = User-provided store backing up the region. If $(D - ParentAllocator) is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator), memory is assumed to - have been allocated with `ParentAllocator`. - n = Bytes to allocate using `ParentAllocator`. This constructor is only - defined If `ParentAllocator` is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator). If - `parent.allocate(n)` returns `null`, the region will be initialized - as empty (correctly initialized but unable to allocate). + store = User-provided store backing up the region. Assumed to have been + allocated with `ParentAllocator`. + n = Bytes to allocate using `ParentAllocator`. If `parent.allocate(n)` + returns `null`, the region will be initialized as empty (correctly + initialized but unable to allocate). */ this(ubyte[] store) pure nothrow @nogc { - _begin = store.ptr; - _end = store.ptr + store.length; - static if (growDownwards) - _current = roundedEnd(); - else - _current = roundedBegin(); + _impl = store; } /// Ditto - static if (!is(ParentAllocator == NullAllocator) && !stateSize!ParentAllocator) + static if (!stateSize!ParentAllocator) this(size_t n) { this(cast(ubyte[]) (parent.allocate(n.roundUpToAlignment(alignment)))); } /// Ditto - static if (!is(ParentAllocator == NullAllocator) && stateSize!ParentAllocator) + static if (stateSize!ParentAllocator) this(ParentAllocator parent, size_t n) { this.parent = parent; @@ -119,15 +110,13 @@ struct Region(ParentAllocator = NullAllocator, */ /** - If `ParentAllocator` is not $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator) and defines `deallocate`, - the region defines a destructor that uses `ParentAllocator.deallocate` to free the - memory chunk. + If `ParentAllocator` defines `deallocate`, the region defines a destructor + that uses `ParentAllocator.deallocate` to free the memory chunk. */ - static if (!is(ParentAllocator == NullAllocator) - && hasMember!(ParentAllocator, "deallocate")) + static if (hasMember!(ParentAllocator, "deallocate")) ~this() { - parent.deallocate(_begin[0 .. _end - _begin]); + with (_impl) parent.deallocate(_begin[0 .. _end - _begin]); } /** @@ -135,7 +124,7 @@ struct Region(ParentAllocator = NullAllocator, */ size_t goodAllocSize(size_t n) const pure nothrow @safe @nogc { - return n.roundUpToAlignment(alignment); + return _impl.goodAllocSize(n); } /** @@ -156,24 +145,7 @@ struct Region(ParentAllocator = NullAllocator, */ void[] allocate(size_t n) pure nothrow @trusted @nogc { - const rounded = goodAllocSize(n); - if (n == 0 || rounded < n || available < rounded) return null; - - static if (growDownwards) - { - assert(available >= rounded); - auto result = (_current - rounded)[0 .. n]; - assert(result.ptr >= _begin); - _current = result.ptr; - assert(owns(result) == Ternary.yes); - } - else - { - auto result = _current[0 .. n]; - _current += rounded; - } - - return result; + return _impl.allocate(n); } /** @@ -188,57 +160,13 @@ struct Region(ParentAllocator = NullAllocator, */ void[] alignedAllocate(size_t n, uint a) pure nothrow @trusted @nogc { - import std.math.traits : isPowerOf2; - assert(a.isPowerOf2); - - const rounded = goodAllocSize(n); - if (n == 0 || rounded < n || available < rounded) return null; - - static if (growDownwards) - { - auto tmpCurrent = _current - rounded; - auto result = tmpCurrent.alignDownTo(a); - if (result <= tmpCurrent && result >= _begin) - { - _current = result; - return cast(void[]) result[0 .. n]; - } - } - else - { - // Just bump the pointer to the next good allocation - auto newCurrent = _current.alignUpTo(a); - if (newCurrent < _current || newCurrent > _end) - return null; - - auto save = _current; - _current = newCurrent; - auto result = allocate(n); - if (result.ptr) - { - assert(result.length == n); - return result; - } - // Failed, rollback - _current = save; - } - return null; + return _impl.alignedAllocate(n, a); } /// Allocates and returns all memory available to this region. void[] allocateAll() pure nothrow @trusted @nogc { - static if (growDownwards) - { - auto result = _begin[0 .. available]; - _current = _begin; - } - else - { - auto result = _current[0 .. available]; - _current = _end; - } - return result; + return _impl.allocateAll; } /** @@ -249,25 +177,7 @@ struct Region(ParentAllocator = NullAllocator, static if (growDownwards == No.growDownwards) bool expand(ref void[] b, size_t delta) pure nothrow @safe @nogc { - assert(owns(b) == Ternary.yes || b is null); - assert((() @trusted => b.ptr + b.length <= _current)() || b is null); - if (b is null || delta == 0) return delta == 0; - auto newLength = b.length + delta; - if ((() @trusted => _current < b.ptr + b.length + alignment)()) - { - immutable currentGoodSize = this.goodAllocSize(b.length); - immutable newGoodSize = this.goodAllocSize(newLength); - immutable goodDelta = newGoodSize - currentGoodSize; - // This was the last allocation! Allocate some more and we're done. - if (goodDelta == 0 - || (() @trusted => allocate(goodDelta).length == goodDelta)()) - { - b = (() @trusted => b.ptr[0 .. newLength])(); - assert((() @trusted => _current < b.ptr + b.length + alignment)()); - return true; - } - } - return false; + return _impl.expand(b, delta); } /** @@ -281,26 +191,7 @@ struct Region(ParentAllocator = NullAllocator, */ bool deallocate(void[] b) pure nothrow @nogc { - assert(owns(b) == Ternary.yes || b.ptr is null); - auto rounded = goodAllocSize(b.length); - static if (growDownwards) - { - if (b.ptr == _current) - { - _current += rounded; - return true; - } - } - else - { - if (b.ptr + rounded == _current) - { - assert(b.ptr !is null || _current is null); - _current = b.ptr; - return true; - } - } - return false; + return _impl.deallocate(b); } /** @@ -309,15 +200,7 @@ struct Region(ParentAllocator = NullAllocator, */ bool deallocateAll() pure nothrow @nogc { - static if (growDownwards) - { - _current = roundedEnd(); - } - else - { - _current = roundedBegin(); - } - return true; + return _impl.deallocateAll; } /** @@ -332,7 +215,7 @@ struct Region(ParentAllocator = NullAllocator, */ Ternary owns(const void[] b) const pure nothrow @trusted @nogc { - return Ternary(b && (&b[0] >= _begin) && (&b[0] + b.length <= _end)); + return _impl.owns(b); } /** @@ -341,23 +224,13 @@ struct Region(ParentAllocator = NullAllocator, */ Ternary empty() const pure nothrow @safe @nogc { - static if (growDownwards) - return Ternary(_current == roundedEnd()); - else - return Ternary(_current == roundedBegin()); + return _impl.empty; } /// Nonstandard property that returns bytes available for allocation. size_t available() const @safe pure nothrow @nogc { - static if (growDownwards) - { - return _current - _begin; - } - else - { - return _end - _current; - } + return _impl.available; } } @@ -414,17 +287,6 @@ struct Region(ParentAllocator = NullAllocator, testAlloc(sharedReg); } -@system nothrow @nogc unittest -{ - import std.experimental.allocator.mallocator : AlignedMallocator; - import std.typecons : Ternary; - - ubyte[] buf = cast(ubyte[]) AlignedMallocator.instance.alignedAllocate(64, 64); - auto reg = Region!(NullAllocator, 64, Yes.growDownwards)(buf); - assert(reg.alignedAllocate(10, 32).length == 10); - assert(!reg.available); -} - @system nothrow @nogc unittest { // test 'this(ubyte[] store)' constructed regions properly clean up @@ -491,107 +353,428 @@ version (StdUnittest) } /** +A `BorrowedRegion` allocates directly from a user-provided block of memory. -`InSituRegion` is a convenient region that carries its storage within itself -(in the form of a statically-sized array). - -The first template argument is the size of the region and the second is the -needed alignment. Depending on the alignment requested and platform details, -the actual available storage may be smaller than the compile-time parameter. To -make sure that at least `n` bytes are available in the region, use -$(D InSituRegion!(n + a - 1, a)). - -Given that the most frequent use of `InSituRegion` is as a stack allocator, it -allocates starting at the end on systems where stack grows downwards, such that -hot memory is used first. +Unlike a `Region`, a `BorrowedRegion` does not own the memory it allocates from +and will not deallocate that memory upon destruction. Instead, it is the user's +responsibility to ensure that the memory is properly disposed of. +In all other respects, a `BorrowedRegion` behaves exactly like a `Region`. */ -struct InSituRegion(size_t size, size_t minAlign = platformAlignment) +struct BorrowedRegion(uint minAlign = platformAlignment, + Flag!"growDownwards" growDownwards = No.growDownwards) { - import std.algorithm.comparison : max; - import std.conv : to; - import std.traits : hasMember; - import std.typecons : Ternary; - static assert(minAlign.isGoodStaticAlignment); - static assert(size >= minAlign); - version (X86) enum growDownwards = Yes.growDownwards; - else version (X86_64) enum growDownwards = Yes.growDownwards; - else version (ARM) enum growDownwards = Yes.growDownwards; - else version (AArch64) enum growDownwards = Yes.growDownwards; - else version (HPPA) enum growDownwards = No.growDownwards; - else version (PPC) enum growDownwards = Yes.growDownwards; - else version (PPC64) enum growDownwards = Yes.growDownwards; - else version (RISCV32) enum growDownwards = Yes.growDownwards; - else version (RISCV64) enum growDownwards = Yes.growDownwards; - else version (MIPS32) enum growDownwards = Yes.growDownwards; - else version (MIPS64) enum growDownwards = Yes.growDownwards; - else version (SPARC) enum growDownwards = Yes.growDownwards; - else version (SPARC64) enum growDownwards = Yes.growDownwards; - else version (SystemZ) enum growDownwards = Yes.growDownwards; - else static assert(0, "Dunno how the stack grows on this architecture."); + import std.typecons : Ternary; - @disable this(this); + // state + private void* _current, _begin, _end; - // state { - private Region!(NullAllocator, minAlign, growDownwards) _impl; - union + private void* roundedBegin() const pure nothrow @trusted @nogc { - private ubyte[size] _store = void; - private double _forAlignmentOnly1; + return cast(void*) roundUpToAlignment(cast(size_t) _begin, alignment); + } + + private void* roundedEnd() const pure nothrow @trusted @nogc + { + return cast(void*) roundDownToAlignment(cast(size_t) _end, alignment); } - // } /** - An alias for `minAlign`, which must be a valid alignment (nonzero power - of 2). The start of the region and all allocation requests will be rounded - up to a multiple of the alignment. + Constructs a region backed by a user-provided store. - ---- - InSituRegion!(4096) a1; - assert(a1.alignment == platformAlignment); - InSituRegion!(4096, 64) a2; - assert(a2.alignment == 64); - ---- + Params: + store = User-provided store backing up the region. */ - alias alignment = minAlign; - - private void lazyInit() + this(ubyte[] store) pure nothrow @nogc { - assert(!_impl._current); - _impl = typeof(_impl)(_store); - assert(_impl._current.alignedAt(alignment)); + _begin = store.ptr; + _end = store.ptr + store.length; + static if (growDownwards) + _current = roundedEnd(); + else + _current = roundedBegin(); } + /* + TODO: The postblit of `BorrowedRegion` should be disabled because such objects + should not be copied around naively. + */ + /** - Allocates `bytes` and returns them, or `null` if the region cannot - accommodate the request. For efficiency reasons, if $(D bytes == 0) the - function returns an empty non-null slice. + Rounds the given size to a multiple of the `alignment` */ - void[] allocate(size_t n) + size_t goodAllocSize(size_t n) const pure nothrow @safe @nogc { - // Fast path - entry: - auto result = _impl.allocate(n); - if (result.length == n) return result; - // Slow path - if (_impl._current) return null; // no more room - lazyInit; - assert(_impl._current); - goto entry; + return n.roundUpToAlignment(alignment); } /** - As above, but the memory allocated is aligned at `a` bytes. + Alignment offered. */ - void[] alignedAllocate(size_t n, uint a) - { - // Fast path - entry: - auto result = _impl.alignedAllocate(n, a); - if (result.length == n) return result; - // Slow path + alias alignment = minAlign; + + /** + Allocates `n` bytes of memory. The shortest path involves an alignment + adjustment (if $(D alignment > 1)), an increment, and a comparison. + + Params: + n = number of bytes to allocate + + Returns: + A properly-aligned buffer of size `n` or `null` if request could not + be satisfied. + */ + void[] allocate(size_t n) pure nothrow @trusted @nogc + { + const rounded = goodAllocSize(n); + if (n == 0 || rounded < n || available < rounded) return null; + + static if (growDownwards) + { + assert(available >= rounded); + auto result = (_current - rounded)[0 .. n]; + assert(result.ptr >= _begin); + _current = result.ptr; + assert(owns(result) == Ternary.yes); + } + else + { + auto result = _current[0 .. n]; + _current += rounded; + } + + return result; + } + + /** + Allocates `n` bytes of memory aligned at alignment `a`. + + Params: + n = number of bytes to allocate + a = alignment for the allocated block + + Returns: + Either a suitable block of `n` bytes aligned at `a`, or `null`. + */ + void[] alignedAllocate(size_t n, uint a) pure nothrow @trusted @nogc + { + import std.math.traits : isPowerOf2; + assert(a.isPowerOf2); + + const rounded = goodAllocSize(n); + if (n == 0 || rounded < n || available < rounded) return null; + + static if (growDownwards) + { + auto tmpCurrent = _current - rounded; + auto result = tmpCurrent.alignDownTo(a); + if (result <= tmpCurrent && result >= _begin) + { + _current = result; + return cast(void[]) result[0 .. n]; + } + } + else + { + // Just bump the pointer to the next good allocation + auto newCurrent = _current.alignUpTo(a); + if (newCurrent < _current || newCurrent > _end) + return null; + + auto save = _current; + _current = newCurrent; + auto result = allocate(n); + if (result.ptr) + { + assert(result.length == n); + return result; + } + // Failed, rollback + _current = save; + } + return null; + } + + /// Allocates and returns all memory available to this region. + void[] allocateAll() pure nothrow @trusted @nogc + { + static if (growDownwards) + { + auto result = _begin[0 .. available]; + _current = _begin; + } + else + { + auto result = _current[0 .. available]; + _current = _end; + } + return result; + } + + /** + Expands an allocated block in place. Expansion will succeed only if the + block is the last allocated. Defined only if `growDownwards` is + `No.growDownwards`. + */ + static if (growDownwards == No.growDownwards) + bool expand(ref void[] b, size_t delta) pure nothrow @safe @nogc + { + assert(owns(b) == Ternary.yes || b is null); + assert((() @trusted => b.ptr + b.length <= _current)() || b is null); + if (b is null || delta == 0) return delta == 0; + auto newLength = b.length + delta; + if ((() @trusted => _current < b.ptr + b.length + alignment)()) + { + immutable currentGoodSize = this.goodAllocSize(b.length); + immutable newGoodSize = this.goodAllocSize(newLength); + immutable goodDelta = newGoodSize - currentGoodSize; + // This was the last allocation! Allocate some more and we're done. + if (goodDelta == 0 + || (() @trusted => allocate(goodDelta).length == goodDelta)()) + { + b = (() @trusted => b.ptr[0 .. newLength])(); + assert((() @trusted => _current < b.ptr + b.length + alignment)()); + return true; + } + } + return false; + } + + /** + Deallocates `b`. This works only if `b` was obtained as the last call + to `allocate`; otherwise (i.e. another allocation has occurred since) it + does nothing. + + Params: + b = Block previously obtained by a call to `allocate` against this + allocator (`null` is allowed). + */ + bool deallocate(void[] b) pure nothrow @nogc + { + assert(owns(b) == Ternary.yes || b.ptr is null); + auto rounded = goodAllocSize(b.length); + static if (growDownwards) + { + if (b.ptr == _current) + { + _current += rounded; + return true; + } + } + else + { + if (b.ptr + rounded == _current) + { + assert(b.ptr !is null || _current is null); + _current = b.ptr; + return true; + } + } + return false; + } + + /** + Deallocates all memory allocated by this region, which can be subsequently + reused for new allocations. + */ + bool deallocateAll() pure nothrow @nogc + { + static if (growDownwards) + { + _current = roundedEnd(); + } + else + { + _current = roundedBegin(); + } + return true; + } + + /** + Queries whether `b` has been allocated with this region. + + Params: + b = Arbitrary block of memory (`null` is allowed; `owns(null)` returns + `false`). + + Returns: + `true` if `b` has been allocated with this region, `false` otherwise. + */ + Ternary owns(const void[] b) const pure nothrow @trusted @nogc + { + return Ternary(b && (&b[0] >= _begin) && (&b[0] + b.length <= _end)); + } + + /** + Returns `Ternary.yes` if no memory has been allocated in this region, + `Ternary.no` otherwise. (Never returns `Ternary.unknown`.) + */ + Ternary empty() const pure nothrow @safe @nogc + { + static if (growDownwards) + return Ternary(_current == roundedEnd()); + else + return Ternary(_current == roundedBegin()); + } + + /// Nonstandard property that returns bytes available for allocation. + size_t available() const @safe pure nothrow @nogc + { + static if (growDownwards) + { + return _current - _begin; + } + else + { + return _end - _current; + } + } +} + +/// +@system nothrow @nogc unittest +{ + import std.typecons : Ternary; + + ubyte[1024] store; + auto myRegion = BorrowedRegion!(1)(store[]); + + assert(myRegion.empty == Ternary.yes); + assert(myRegion.available == store.length); + + void[] b = myRegion.allocate(101); + + assert(b.length == 101); + assert(myRegion.empty == Ternary.no); + assert(myRegion.owns(b) == Ternary.yes); + assert(myRegion.available == store.length - b.length); + + void[] b2 = myRegion.allocate(256); + + // Can only free the most recent allocation + assert(myRegion.deallocate(b) == false); + assert(myRegion.deallocate(b2) == true); + + myRegion.deallocateAll(); + + assert(myRegion.empty == Ternary.yes); +} + +@system nothrow @nogc unittest +{ + import std.experimental.allocator.mallocator : AlignedMallocator; + import std.typecons : Ternary; + + ubyte[] buf = cast(ubyte[]) AlignedMallocator.instance.alignedAllocate(64, 64); + auto reg = BorrowedRegion!(64, Yes.growDownwards)(buf); + assert(reg.alignedAllocate(10, 32).length == 10); + assert(!reg.available); +} + +/** + +`InSituRegion` is a convenient region that carries its storage within itself +(in the form of a statically-sized array). + +The first template argument is the size of the region and the second is the +needed alignment. Depending on the alignment requested and platform details, +the actual available storage may be smaller than the compile-time parameter. To +make sure that at least `n` bytes are available in the region, use +$(D InSituRegion!(n + a - 1, a)). + +Given that the most frequent use of `InSituRegion` is as a stack allocator, it +allocates starting at the end on systems where stack grows downwards, such that +hot memory is used first. + +*/ +struct InSituRegion(size_t size, size_t minAlign = platformAlignment) +{ + import std.algorithm.comparison : max; + import std.conv : to; + import std.traits : hasMember; + import std.typecons : Ternary; + + static assert(minAlign.isGoodStaticAlignment); + static assert(size >= minAlign); + + version (X86) enum growDownwards = Yes.growDownwards; + else version (X86_64) enum growDownwards = Yes.growDownwards; + else version (ARM) enum growDownwards = Yes.growDownwards; + else version (AArch64) enum growDownwards = Yes.growDownwards; + else version (HPPA) enum growDownwards = No.growDownwards; + else version (PPC) enum growDownwards = Yes.growDownwards; + else version (PPC64) enum growDownwards = Yes.growDownwards; + else version (RISCV32) enum growDownwards = Yes.growDownwards; + else version (RISCV64) enum growDownwards = Yes.growDownwards; + else version (MIPS32) enum growDownwards = Yes.growDownwards; + else version (MIPS64) enum growDownwards = Yes.growDownwards; + else version (SPARC) enum growDownwards = Yes.growDownwards; + else version (SPARC64) enum growDownwards = Yes.growDownwards; + else version (SystemZ) enum growDownwards = Yes.growDownwards; + else static assert(0, "Dunno how the stack grows on this architecture."); + + @disable this(this); + + // state { + private BorrowedRegion!(minAlign, growDownwards) _impl; + union + { + private ubyte[size] _store = void; + private double _forAlignmentOnly1; + } + // } + + /** + An alias for `minAlign`, which must be a valid alignment (nonzero power + of 2). The start of the region and all allocation requests will be rounded + up to a multiple of the alignment. + + ---- + InSituRegion!(4096) a1; + assert(a1.alignment == platformAlignment); + InSituRegion!(4096, 64) a2; + assert(a2.alignment == 64); + ---- + */ + alias alignment = minAlign; + + private void lazyInit() + { + assert(!_impl._current); + _impl = typeof(_impl)(_store); + assert(_impl._current.alignedAt(alignment)); + } + + /** + Allocates `bytes` and returns them, or `null` if the region cannot + accommodate the request. For efficiency reasons, if $(D bytes == 0) the + function returns an empty non-null slice. + */ + void[] allocate(size_t n) + { + // Fast path + entry: + auto result = _impl.allocate(n); + if (result.length == n) return result; + // Slow path + if (_impl._current) return null; // no more room + lazyInit; + assert(_impl._current); + goto entry; + } + + /** + As above, but the memory allocated is aligned at `a` bytes. + */ + void[] alignedAllocate(size_t n, uint a) + { + // Fast path + entry: + auto result = _impl.alignedAllocate(n, a); + if (result.length == n) return result; + // Slow path if (_impl._current) return null; // no more room lazyInit; assert(_impl._current); @@ -992,7 +1175,7 @@ version (Posix) @system nothrow @nogc unittest The threadsafe version of the `Region` allocator. Allocations and deallocations are lock-free based using $(REF cas, core,atomic). */ -shared struct SharedRegion(ParentAllocator = NullAllocator, +shared struct SharedRegion(ParentAllocator, uint minAlign = platformAlignment, Flag!"growDownwards" growDownwards = No.growDownwards) { @@ -1016,45 +1199,36 @@ shared struct SharedRegion(ParentAllocator = NullAllocator, { alias parent = ParentAllocator.instance; } - private shared void* _current, _begin, _end; + private shared SharedBorrowedRegion!(minAlign, growDownwards) _impl; private void* roundedBegin() const pure nothrow @trusted @nogc { - return cast(void*) roundUpToAlignment(cast(size_t) _begin, alignment); + return _impl.roundedBegin; } private void* roundedEnd() const pure nothrow @trusted @nogc { - return cast(void*) roundDownToAlignment(cast(size_t) _end, alignment); + return _impl.roundedEnd; } /** Constructs a region backed by a user-provided store. - Assumes the memory was allocated with `ParentAllocator` - (if different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator)). + Assumes the memory was allocated with `ParentAllocator`. Params: - store = User-provided store backing up the region. If `ParentAllocator` - is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator), memory is assumed to - have been allocated with `ParentAllocator`. - n = Bytes to allocate using `ParentAllocator`. This constructor is only - defined If `ParentAllocator` is different from $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator). If - `parent.allocate(n)` returns `null`, the region will be initialized - as empty (correctly initialized but unable to allocate). + store = User-provided store backing up the region. Assumed to have been + allocated with `ParentAllocator`. + n = Bytes to allocate using `ParentAllocator`. If `parent.allocate(n)` + returns `null`, the region will be initialized as empty (correctly + initialized but unable to allocate). */ this(ubyte[] store) pure nothrow @nogc { - _begin = cast(typeof(_begin)) store.ptr; - _end = cast(typeof(_end)) (store.ptr + store.length); - static if (growDownwards) - _current = cast(typeof(_current)) roundedEnd(); - else - _current = cast(typeof(_current)) roundedBegin(); + _impl = store; } /// Ditto - static if (!is(ParentAllocator == NullAllocator)) this(size_t n) { this(cast(ubyte[]) (parent.allocate(n.roundUpToAlignment(alignment)))); @@ -1065,7 +1239,7 @@ shared struct SharedRegion(ParentAllocator = NullAllocator, */ size_t goodAllocSize(size_t n) const pure nothrow @safe @nogc { - return n.roundUpToAlignment(alignment); + return _impl.goodAllocSize(n); } /** @@ -1086,38 +1260,7 @@ shared struct SharedRegion(ParentAllocator = NullAllocator, */ void[] allocate(size_t n) pure nothrow @trusted @nogc { - import core.atomic : cas, atomicLoad; - - if (n == 0) return null; - const rounded = goodAllocSize(n); - - shared void* localCurrent, localNewCurrent; - static if (growDownwards) - { - do - { - localCurrent = atomicLoad(_current); - localNewCurrent = localCurrent - rounded; - if (localNewCurrent > localCurrent || localNewCurrent < _begin) - return null; - } while (!cas(&_current, localCurrent, localNewCurrent)); - - return cast(void[]) localNewCurrent[0 .. n]; - } - else - { - do - { - localCurrent = atomicLoad(_current); - localNewCurrent = localCurrent + rounded; - if (localNewCurrent < localCurrent || localNewCurrent > _end) - return null; - } while (!cas(&_current, localCurrent, localNewCurrent)); - - return cast(void[]) localCurrent[0 .. n]; - } - - assert(0, "Unexpected error in SharedRegion.allocate"); + return _impl.allocate(n); } /** @@ -1131,97 +1274,30 @@ shared struct SharedRegion(ParentAllocator = NullAllocator, */ bool deallocate(void[] b) pure nothrow @nogc { - import core.atomic : cas, atomicLoad; - - const rounded = goodAllocSize(b.length); - shared void* localCurrent, localNewCurrent; - - // The cas is done only once, because only the last allocation can be reverted - localCurrent = atomicLoad(_current); - static if (growDownwards) - { - localNewCurrent = localCurrent + rounded; - if (b.ptr == localCurrent) - return cas(&_current, localCurrent, localNewCurrent); - } - else - { - localNewCurrent = localCurrent - rounded; - if (b.ptr == localNewCurrent) - return cas(&_current, localCurrent, localNewCurrent); - } - - return false; - } - - /** - Deallocates all memory allocated by this region, which can be subsequently - reused for new allocations. - */ - bool deallocateAll() pure nothrow @nogc - { - import core.atomic : atomicStore; - static if (growDownwards) - { - atomicStore(_current, cast(shared(void*)) roundedEnd()); - } - else - { - atomicStore(_current, cast(shared(void*)) roundedBegin()); - } - return true; + return _impl.deallocate(b); } - /** - Allocates `n` bytes of memory aligned at alignment `a`. - Params: - n = number of bytes to allocate - a = alignment for the allocated block - - Returns: - Either a suitable block of `n` bytes aligned at `a`, or `null`. - */ - void[] alignedAllocate(size_t n, uint a) pure nothrow @trusted @nogc - { - import core.atomic : cas, atomicLoad; - import std.math.traits : isPowerOf2; - - assert(a.isPowerOf2); - if (n == 0) return null; - - const rounded = goodAllocSize(n); - shared void* localCurrent, localNewCurrent; - - static if (growDownwards) - { - do - { - localCurrent = atomicLoad(_current); - auto alignedCurrent = cast(void*)(localCurrent - rounded); - localNewCurrent = cast(shared(void*)) alignedCurrent.alignDownTo(a); - if (alignedCurrent > localCurrent || localNewCurrent > alignedCurrent || - localNewCurrent < _begin) - return null; - } while (!cas(&_current, localCurrent, localNewCurrent)); - - return cast(void[]) localNewCurrent[0 .. n]; - } - else - { - do - { - localCurrent = atomicLoad(_current); - auto alignedCurrent = alignUpTo(cast(void*) localCurrent, a); - localNewCurrent = cast(shared(void*)) (alignedCurrent + rounded); - if (alignedCurrent < localCurrent || localNewCurrent < alignedCurrent || - localNewCurrent > _end) - return null; - } while (!cas(&_current, localCurrent, localNewCurrent)); - - return cast(void[]) (localNewCurrent - rounded)[0 .. n]; - } + /** + Deallocates all memory allocated by this region, which can be subsequently + reused for new allocations. + */ + bool deallocateAll() pure nothrow @nogc + { + return _impl.deallocateAll; + } + + /** + Allocates `n` bytes of memory aligned at alignment `a`. + Params: + n = number of bytes to allocate + a = alignment for the allocated block - assert(0, "Unexpected error in SharedRegion.alignedAllocate"); + Returns: + Either a suitable block of `n` bytes aligned at `a`, or `null`. + */ + void[] alignedAllocate(size_t n, uint a) pure nothrow @trusted @nogc + { + return _impl.alignedAllocate(n, a); } /** @@ -1236,7 +1312,7 @@ shared struct SharedRegion(ParentAllocator = NullAllocator, */ Ternary owns(const void[] b) const pure nothrow @trusted @nogc { - return Ternary(b && (&b[0] >= _begin) && (&b[0] + b.length <= _end)); + return _impl.owns(b); } /** @@ -1245,25 +1321,17 @@ shared struct SharedRegion(ParentAllocator = NullAllocator, */ Ternary empty() const pure nothrow @safe @nogc { - import core.atomic : atomicLoad; - - auto localCurrent = atomicLoad(_current); - static if (growDownwards) - return Ternary(localCurrent == roundedEnd()); - else - return Ternary(localCurrent == roundedBegin()); + return _impl.empty; } /** - If `ParentAllocator` is not $(REF_ALTTEXT `NullAllocator`, NullAllocator, std,experimental,allocator,building_blocks,null_allocator) and defines `deallocate`, - the region defines a destructor that uses `ParentAllocator.deallocate` to free the - memory chunk. + If `ParentAllocator` defines `deallocate`, the region defines a destructor + that uses `ParentAllocator.deallocate` to free the memory chunk. */ - static if (!is(ParentAllocator == NullAllocator) - && hasMember!(ParentAllocator, "deallocate")) + static if (hasMember!(ParentAllocator, "deallocate")) ~this() { - parent.deallocate(cast(void[]) _begin[0 .. _end - _begin]); + with (_impl) parent.deallocate(cast(void[]) _begin[0 .. _end - _begin]); } } @@ -1397,3 +1465,250 @@ shared struct SharedRegion(ParentAllocator = NullAllocator, testAlloc(a1, true); testAlloc(a2, false); } + +/** +A `SharedBorrowedRegion` allocates directly from a user-provided block of memory. + +Unlike a `SharedRegion`, a `SharedBorrowedRegion` does not own the memory it +allocates from and will not deallocate that memory upon destruction. Instead, +it is the user's responsibility to ensure that the memory is properly disposed +of. + +In all other respects, a `SharedBorrowedRegion` behaves exactly like a `SharedRegion`. +*/ +shared struct SharedBorrowedRegion(uint minAlign = platformAlignment, + Flag!"growDownwards" growDownwards = No.growDownwards) +{ + static assert(minAlign.isGoodStaticAlignment); + + import std.typecons : Ternary; + + // state + private void* _current, _begin, _end; + + private void* roundedBegin() shared const pure nothrow @trusted @nogc + { + return cast(void*) roundUpToAlignment(cast(size_t) _begin, alignment); + } + + private void* roundedEnd() shared const pure nothrow @trusted @nogc + { + return cast(void*) roundDownToAlignment(cast(size_t) _end, alignment); + } + + /** + Constructs a region backed by a user-provided store. + + Params: + store = User-provided store backing up the region. Must not be aliased. + */ + this(ubyte[] store) shared pure nothrow @nogc + { + _begin = cast(typeof(_begin)) store.ptr; + _end = cast(typeof(_end)) (store.ptr + store.length); + static if (growDownwards) + _current = cast(typeof(_current)) roundedEnd(); + else + _current = cast(typeof(_current)) roundedBegin(); + } + + /* + TODO: The postblit of `SharedBorrowedRegion` should be disabled because + such objects should not be copied around naively. + */ + + /** + Rounds the given size to a multiple of the `alignment` + */ + size_t goodAllocSize(size_t n) shared const pure nothrow @safe @nogc + { + return n.roundUpToAlignment(alignment); + } + + /** + Alignment offered. + */ + alias alignment = minAlign; + + /** + Allocates `n` bytes of memory. The allocation is served by atomically incrementing + a pointer which keeps track of the current used space. + + Params: + n = number of bytes to allocate + + Returns: + A properly-aligned buffer of size `n`, or `null` if request could not + be satisfied. + */ + void[] allocate(size_t n) shared pure nothrow @trusted @nogc + { + import core.atomic : cas, atomicLoad; + + if (n == 0) return null; + const rounded = goodAllocSize(n); + + shared void* localCurrent, localNewCurrent; + static if (growDownwards) + { + do + { + localCurrent = atomicLoad(_current); + localNewCurrent = localCurrent - rounded; + if (localNewCurrent > localCurrent || localNewCurrent < _begin) + return null; + } while (!cas(&_current, localCurrent, localNewCurrent)); + + return cast(void[]) localNewCurrent[0 .. n]; + } + else + { + do + { + localCurrent = atomicLoad(_current); + localNewCurrent = localCurrent + rounded; + if (localNewCurrent < localCurrent || localNewCurrent > _end) + return null; + } while (!cas(&_current, localCurrent, localNewCurrent)); + + return cast(void[]) localCurrent[0 .. n]; + } + + assert(0, "Unexpected error in SharedBorrowedRegion.allocate"); + } + + /** + Allocates `n` bytes of memory aligned at alignment `a`. + + Params: + n = number of bytes to allocate + a = alignment for the allocated block + + Returns: + Either a suitable block of `n` bytes aligned at `a`, or `null`. + */ + void[] alignedAllocate(size_t n, uint a) shared pure nothrow @trusted @nogc + { + import core.atomic : cas, atomicLoad; + import std.math.traits : isPowerOf2; + + assert(a.isPowerOf2); + if (n == 0) return null; + + const rounded = goodAllocSize(n); + shared void* localCurrent, localNewCurrent; + + static if (growDownwards) + { + do + { + localCurrent = atomicLoad(_current); + auto alignedCurrent = cast(void*)(localCurrent - rounded); + localNewCurrent = cast(shared(void*)) alignedCurrent.alignDownTo(a); + if (alignedCurrent > localCurrent || localNewCurrent > alignedCurrent || + localNewCurrent < _begin) + return null; + } while (!cas(&_current, localCurrent, localNewCurrent)); + + return cast(void[]) localNewCurrent[0 .. n]; + } + else + { + do + { + localCurrent = atomicLoad(_current); + auto alignedCurrent = alignUpTo(cast(void*) localCurrent, a); + localNewCurrent = cast(shared(void*)) (alignedCurrent + rounded); + if (alignedCurrent < localCurrent || localNewCurrent < alignedCurrent || + localNewCurrent > _end) + return null; + } while (!cas(&_current, localCurrent, localNewCurrent)); + + return cast(void[]) (localNewCurrent - rounded)[0 .. n]; + } + + assert(0, "Unexpected error in SharedBorrowedRegion.alignedAllocate"); + } + + /** + Deallocates `b`. This works only if `b` was obtained as the last call + to `allocate`; otherwise (i.e. another allocation has occurred since) it + does nothing. + + Params: + b = Block previously obtained by a call to `allocate` against this + allocator (`null` is allowed). + */ + bool deallocate(void[] b) shared pure nothrow @nogc + { + import core.atomic : cas, atomicLoad; + + const rounded = goodAllocSize(b.length); + shared void* localCurrent, localNewCurrent; + + // The cas is done only once, because only the last allocation can be reverted + localCurrent = atomicLoad(_current); + static if (growDownwards) + { + localNewCurrent = localCurrent + rounded; + if (b.ptr == localCurrent) + return cas(&_current, localCurrent, localNewCurrent); + } + else + { + localNewCurrent = localCurrent - rounded; + if (b.ptr == localNewCurrent) + return cas(&_current, localCurrent, localNewCurrent); + } + + return false; + } + + /** + Deallocates all memory allocated by this region, which can be subsequently + reused for new allocations. + */ + bool deallocateAll() shared pure nothrow @nogc + { + import core.atomic : atomicStore; + static if (growDownwards) + { + atomicStore(_current, cast(shared(void*)) roundedEnd()); + } + else + { + atomicStore(_current, cast(shared(void*)) roundedBegin()); + } + return true; + } + + /** + Queries whether `b` has been allocated with this region. + + Params: + b = Arbitrary block of memory (`null` is allowed; `owns(null)` returns + `false`). + + Returns: + `true` if `b` has been allocated with this region, `false` otherwise. + */ + Ternary owns(const void[] b) shared const pure nothrow @trusted @nogc + { + return Ternary(b && (&b[0] >= _begin) && (&b[0] + b.length <= _end)); + } + + /** + Returns `Ternary.yes` if no memory has been allocated in this region, + `Ternary.no` otherwise. (Never returns `Ternary.unknown`.) + */ + Ternary empty() shared const pure nothrow @safe @nogc + { + import core.atomic : atomicLoad; + + auto localCurrent = atomicLoad(_current); + static if (growDownwards) + return Ternary(localCurrent == roundedEnd()); + else + return Ternary(localCurrent == roundedBegin()); + } +} diff --git a/std/experimental/allocator/building_blocks/scoped_allocator.d b/std/experimental/allocator/building_blocks/scoped_allocator.d index 8fc7584f796..96859b0762f 100644 --- a/std/experimental/allocator/building_blocks/scoped_allocator.d +++ b/std/experimental/allocator/building_blocks/scoped_allocator.d @@ -259,10 +259,10 @@ version (StdUnittest) // Test that deallocateAll infers from parent @system unittest { - import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; - ScopedAllocator!(Region!()) a; - a.parent.parent = Region!()(new ubyte[1024 * 64]); + ScopedAllocator!(BorrowedRegion!()) a; + a.parent.parent = BorrowedRegion!()(new ubyte[1024 * 64]); auto b = a.allocate(42); assert(b.length == 42); assert((() pure nothrow @safe @nogc => a.expand(b, 22))()); diff --git a/std/experimental/allocator/building_blocks/segregator.d b/std/experimental/allocator/building_blocks/segregator.d index 655db4567ce..ff089bddc13 100644 --- a/std/experimental/allocator/building_blocks/segregator.d +++ b/std/experimental/allocator/building_blocks/segregator.d @@ -503,12 +503,12 @@ if (Args.length > 3) @system unittest { - import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; import std.typecons : Ternary; - auto a = Segregator!(10_240, Region!(), Region!())( - Region!()(new ubyte[4096 * 1024]), - Region!()(new ubyte[4096 * 1024])); + auto a = Segregator!(10_240, BorrowedRegion!(), BorrowedRegion!())( + BorrowedRegion!()(new ubyte[4096 * 1024]), + BorrowedRegion!()(new ubyte[4096 * 1024])); assert((() nothrow @safe @nogc => a.empty)() == Ternary.yes); auto b = a.alignedAllocate(42, 8); diff --git a/std/experimental/allocator/building_blocks/stats_collector.d b/std/experimental/allocator/building_blocks/stats_collector.d index d57b3edd16f..3770af10ceb 100644 --- a/std/experimental/allocator/building_blocks/stats_collector.d +++ b/std/experimental/allocator/building_blocks/stats_collector.d @@ -845,9 +845,9 @@ public: @system unittest { - import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; - auto a = StatsCollector!(Region!(), Options.all, Options.all)(Region!()(new ubyte[1024 * 64])); + auto a = StatsCollector!(BorrowedRegion!(), Options.all, Options.all)(BorrowedRegion!()(new ubyte[1024 * 64])); auto b = a.allocate(42); assert(b.length == 42); // Test that reallocate infers from parent @@ -859,9 +859,9 @@ public: @system unittest { - import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; - auto a = StatsCollector!(Region!(), Options.all)(Region!()(new ubyte[1024 * 64])); + auto a = StatsCollector!(BorrowedRegion!(), Options.all)(BorrowedRegion!()(new ubyte[1024 * 64])); auto b = a.alignedAllocate(42, 128); assert(b.length == 42); assert(b.ptr.alignedAt(128)); diff --git a/std/experimental/allocator/package.d b/std/experimental/allocator/package.d index 62f848f11fc..217792676b5 100644 --- a/std/experimental/allocator/package.d +++ b/std/experimental/allocator/package.d @@ -547,24 +547,24 @@ nothrow: @system unittest { - import std.experimental.allocator.building_blocks.region : Region; + import std.experimental.allocator.building_blocks.region : BorrowedRegion; import std.conv : emplace; - auto reg = Region!()(new ubyte[1024]); - auto state = reg.allocate(stateSize!(CAllocatorImpl!(Region!(), Yes.indirect))); - auto regObj = emplace!(CAllocatorImpl!(Region!(), Yes.indirect))(state, ®); + auto reg = BorrowedRegion!()(new ubyte[1024]); + auto state = reg.allocate(stateSize!(CAllocatorImpl!(BorrowedRegion!(), Yes.indirect))); + auto regObj = emplace!(CAllocatorImpl!(BorrowedRegion!(), Yes.indirect))(state, ®); auto rcalloc = RCIAllocator(regObj); auto b = rcalloc.allocate(10); assert(b.length == 10); // The reference counting is zero based - assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 1); + assert((cast(CAllocatorImpl!(BorrowedRegion!(), Yes.indirect))(rcalloc._alloc)).rc == 1); { auto rca2 = rcalloc; - assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 2); + assert((cast(CAllocatorImpl!(BorrowedRegion!(), Yes.indirect))(rcalloc._alloc)).rc == 2); } - assert((cast(CAllocatorImpl!(Region!(), Yes.indirect))(rcalloc._alloc)).rc == 1); + assert((cast(CAllocatorImpl!(BorrowedRegion!(), Yes.indirect))(rcalloc._alloc)).rc == 1); } @system unittest diff --git a/std/experimental/typecons.d b/std/experimental/typecons.d deleted file mode 100644 index 46e21e77e7a..00000000000 --- a/std/experimental/typecons.d +++ /dev/null @@ -1,1083 +0,0 @@ -// Written in the D programming language. - -/** -This module implements experimental additions/modifications to $(MREF std, typecons). - -Use this module to test out new functionality for $(REF wrap, std, typecons) -which allows for a struct to be wrapped against an interface; the -implementation in $(MREF std, typecons) only allows for classes to use the wrap -functionality. - -Source: $(PHOBOSSRC std/experimental/typecons.d) - -Copyright: Copyright the respective authors, 2008- -License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). -Authors: $(HTTP erdani.org, Andrei Alexandrescu), - $(HTTP bartoszmilewski.wordpress.com, Bartosz Milewski), - Don Clugston, - Shin Fujishiro, - Kenji Hara - */ -module std.experimental.typecons; - -import std.meta; // : AliasSeq, allSatisfy; -import std.traits; - -import std.typecons : Tuple, tuple, Bind, DerivedFunctionType, GetOverloadedMethods; - -private -{ - pragma(mangle, "_d_toObject") - extern(C) pure nothrow Object typecons_d_toObject(void* p); -} - -/* - * Avoids opCast operator overloading. - */ -private template dynamicCast(T) -if (is(T == class) || is(T == interface)) -{ - @trusted - T dynamicCast(S)(inout S source) - if (is(S == class) || is(S == interface)) - { - static if (is(Unqual!S : Unqual!T)) - { - import std.traits : QualifierOf; - alias Qual = QualifierOf!S; // SharedOf or MutableOf - alias TmpT = Qual!(Unqual!T); - inout(TmpT) tmp = source; // bypass opCast by implicit conversion - return *cast(T*)(&tmp); // + variable pointer cast + dereference - } - else - { - return cast(T) typecons_d_toObject(*cast(void**)(&source)); - } - } -} - -@system unittest -{ - class C { @disable void opCast(T)(); } - auto c = new C; - static assert(!__traits(compiles, cast(Object) c)); - auto o = dynamicCast!Object(c); - assert(c is o); - - interface I { @disable void opCast(T)(); Object instance(); } - interface J { @disable void opCast(T)(); Object instance(); } - class D : I, J { Object instance() { return this; } } - I i = new D(); - static assert(!__traits(compiles, cast(J) i)); - J j = dynamicCast!J(i); - assert(i.instance() is j.instance()); -} - -/* - * Determines if the `Source` type satisfies all interface requirements of - * `Targets`. - */ -private template implementsInterface(Source, Targets...) -if (Targets.length >= 1 && allSatisfy!(isMutable, Targets)) -{ - import std.meta : staticMap; - - // strict upcast - bool implementsInterface()() - if (Targets.length == 1 && is(Source : Targets[0])) - { - return true; - } - // structural upcast - template implementsInterface() - if (!allSatisfy!(Bind!(isImplicitlyConvertible, Source), Targets)) - { - auto implementsInterface() - { - return hasRequiredMethods!(); - } - - // list of FuncInfo - alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!Targets); - // list of function symbols - alias SourceMembers = GetOverloadedMethods!Source; - - // Check whether all of SourceMembers satisfy covariance target in - // TargetMembers - template hasRequiredMethods(size_t i = 0) - { - static if (i >= TargetMembers.length) - enum hasRequiredMethods = true; - else - { - enum foundFunc = findCovariantFunction!(TargetMembers[i], Source, SourceMembers); - - version (StdUnittest) {} - else debug - { - static if (foundFunc == -1) - pragma(msg, "Could not locate matching function for: ", - TargetMembers[i].stringof); - } - - enum hasRequiredMethods = - foundFunc != -1 && - hasRequiredMethods!(i + 1); - } - } - } -} -// ditto -private template implementsInterface(Source, Targets...) -if (Targets.length >= 1 && !allSatisfy!(isMutable, Targets)) -{ - import std.meta : staticMap; - - alias implementsInterface = .implementsInterface!(Source, staticMap!(Unqual, Targets)); -} - -@safe unittest -{ - interface Foo { - void foo(); - } - interface Bar { - void bar(); - } - interface FooBar : Foo, Bar { - void foobar(); - } - - struct A { - void foo() {} - } - struct B { - void bar() {} - void foobar() {} - } - class C { - void foo() {} - void bar() {} - } - struct D { - void foo() {} - void bar() {} - void foobar() {} - } - // Implements interface - static assert(implementsInterface!(A, Foo)); - static assert(implementsInterface!(A, const(Foo))); - static assert(implementsInterface!(A, immutable(Foo))); - // Doesn't implement interface - static assert(!implementsInterface!(B, Foo)); - static assert(implementsInterface!(B, Bar)); - // Implements both interfaces - static assert(implementsInterface!(C, Foo)); - static assert(implementsInterface!(C, Bar)); - static assert(implementsInterface!(C, Foo, Bar)); - static assert(implementsInterface!(C, Foo, const(Bar))); - static assert(!implementsInterface!(A, Foo, Bar)); - static assert(!implementsInterface!(A, Foo, immutable(Bar))); - // Implements inherited - static assert(implementsInterface!(D, FooBar)); - static assert(!implementsInterface!(B, FooBar)); -} - -private enum isInterface(ConceptType) = is(ConceptType == interface); - -/// -template wrap(Targets...) -if (Targets.length >= 1 && allSatisfy!(isInterface, Targets)) -{ - import std.meta : ApplyLeft, staticMap; - - version (StdDdoc) - { - /** - * Wrap src in an anonymous class implementing $(D_PARAM Targets). - * - * wrap creates an internal wrapper class which implements the - * interfaces in `Targets` using the methods of `src`, then returns a - * GC-allocated instance of it. - * - * $(D_PARAM Source) can be either a `class` or a `struct`, but it must - * $(I structurally conform) with all the $(D_PARAM Targets) - * interfaces; i.e. it must provide concrete methods with compatible - * signatures of those in $(D_PARAM Targets). - * - * If $(D_PARAM Source) is a `struct` then wrapping/unwrapping will - * create a copy; it is not possible to affect the original `struct` - * through the wrapper. - * - * The returned object additionally supports $(LREF unwrap). - * - * Note: - * If $(D_PARAM Targets) has only one entry and $(D_PARAM Source) is a - * class which explicitly implements it, wrap simply returns src - * upcasted to `Targets[0]`. - * - * Bugs: - * wrap does not support interfaces which take their own type as either - * a parameter type or return type in any of its methods. - * - * See_Also: $(LREF unwrap) for examples - */ - auto wrap(Source)(inout Source src) - if (implementsInterface!(Source, Targets)); - } - - static if (!allSatisfy!(isMutable, Targets)) - alias wrap = .wrap!(staticMap!(Unqual, Targets)); - else - { - // strict upcast - auto wrap(Source)(inout Source src) - if (Targets.length == 1 && is(Source : Targets[0])) - { - alias T = Select!(is(Source == shared), shared Targets[0], Targets[0]); - return dynamicCast!(inout T)(src); - } - - // structural upcast - template wrap(Source) - if (!allSatisfy!(ApplyLeft!(isImplicitlyConvertible, Source), Targets)) - { - auto wrap(inout Source src) - { - static assert(implementsInterface!(Source, Targets), - "Source "~Source.stringof~ - " does not have structural conformance to "~ - Targets.stringof); - - alias T = Select!(is(Source == shared), shared Impl, Impl); - return new inout T(src); - } - - // list of FuncInfo - alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!(Targets)); - // list of function symbols - alias SourceMembers = GetOverloadedMethods!Source; - - static if (is(Source == class) || is(Source == interface)) - alias StructuralType = Object; - else static if (is(Source == struct)) - alias StructuralType = Source; - - // Check whether all of SourceMembers satisfy covariance target in TargetMembers - // Internal wrapper class - final class Impl : Structural!StructuralType, Targets - { - private: - Source _wrap_source; - - this( inout Source s) inout @safe pure nothrow { _wrap_source = s; } - this(shared inout Source s) shared inout @safe pure nothrow { _wrap_source = s; } - - static if (is(Source == class) || is(Source == interface)) - { - // BUG: making private should work with NVI. - protected inout(Object) _wrap_getSource() inout @safe - { - return dynamicCast!(inout Object)(_wrap_source); - } - } - else - { - // BUG: making private should work with NVI. - protected inout(Source) _wrap_getSource() inout @safe - { - return _wrap_source; - } - } - - import std.conv : to; - import core.lifetime : forward; - template generateFun(size_t i) - { - enum name = TargetMembers[i].name; - enum fa = functionAttributes!(TargetMembers[i].type); - static args(int num)() - { - string r; - bool first = true; - foreach (i; 0 .. num) - { - import std.conv : to; - r ~= (first ? "" : ", ") ~ " a" ~ (i+1).to!string; - first = false; - } - return r; - } - static if (fa & FunctionAttribute.property) - { - static if (Parameters!(TargetMembers[i].type).length == 0) - enum fbody = "_wrap_source."~name; - else - enum fbody = "_wrap_source."~name~" = a1"; - } - else - { - enum fbody = "_wrap_source."~name~"("~args!(Parameters!(TargetMembers[i].type).length)~")"; - } - enum generateFun = - "override "~wrapperSignature!(TargetMembers[i]) ~ - "{ return "~fbody~"; }"; - } - - public: - static foreach (i; 0 .. TargetMembers.length) - mixin(generateFun!i); - } - } - } -} - -// Build a signature that matches the provided function -// Each argument will be provided a name in the form a# -private template wrapperSignature(alias fun) -{ - enum name = fun.name; - enum fa = functionAttributes!(fun.type); - static @property stc() - { - string r; - if (fa & FunctionAttribute.property) r ~= "@property "; - if (fa & FunctionAttribute.ref_) r ~= "ref "; - if (fa & FunctionAttribute.pure_) r ~= "pure "; - if (fa & FunctionAttribute.nothrow_) r ~= "nothrow "; - if (fa & FunctionAttribute.trusted) r ~= "@trusted "; - if (fa & FunctionAttribute.safe) r ~= "@safe "; - return r; - } - static @property mod() - { - alias type = AliasSeq!(fun.type)[0]; - string r; - static if (is(type == immutable)) r ~= " immutable"; - else - { - static if (is(type == shared)) r ~= " shared"; - static if (is(type == const)) r ~= " const"; - else static if (is(type == inout)) r ~= " inout"; - //else --> mutable - } - return r; - } - alias param = Parameters!(fun.type); - static @property wrapperParameters() - { - string r; - bool first = true; - foreach (i, p; param) - { - import std.conv : to; - r ~= (first ? "" : ", ") ~ p.stringof ~ " a" ~ (i+1).to!string; - first = false; - } - return r; - } - - enum wrapperSignature = - stc~ReturnType!(fun.type).stringof ~ " " - ~ name~"("~wrapperParameters~")"~mod; -} - -@safe unittest -{ - interface M - { - void f1(); - void f2(string[] args, int count); - void f3(string[] args, int count) pure const; - } - - alias TargetMembers = UniqMembers!(ConcatInterfaceMembers!M); - static assert(wrapperSignature!(TargetMembers[0]) == "void f1()" - , wrapperSignature!(TargetMembers[0])); - - static assert(wrapperSignature!(TargetMembers[1]) == "void f2(string[] a1, int a2)" - , wrapperSignature!(TargetMembers[1])); - - static assert(wrapperSignature!(TargetMembers[2]) == "pure void f3(string[] a1, int a2) const" - , wrapperSignature!(TargetMembers[2])); -} - -// Internal class to support dynamic cross-casting -private interface Structural(T) -{ - inout(T) _wrap_getSource() inout @safe pure nothrow; -} - -private string unwrapExceptionText(Source, Target)() -{ - return Target.stringof~ " not wrapped into "~ Source.stringof; -} - -version (StdDdoc) -{ - /** - * Extract object previously wrapped by $(LREF wrap). - * - * Params: - * Target = type of wrapped object - * src = wrapper object returned by $(LREF wrap) - * - * Returns: the wrapped object, or null if src is not a wrapper created - * by $(LREF wrap) and $(D_PARAM Target) is a class - * - * Throws: $(REF ConvException, std, conv) when attempting to extract a - * struct which is not the wrapped type - * - * See_also: $(LREF wrap) - */ - public inout(Target) unwrap(Target, Source)(inout Source src); -} - -/// -@system unittest -{ - interface Quack - { - int quack(); - @property int height(); - } - interface Flyer - { - @property int height(); - } - class Duck : Quack - { - int quack() { return 1; } - @property int height() { return 10; } - } - class Human - { - int quack() { return 2; } - @property int height() { return 20; } - } - struct HumanStructure - { - int quack() { return 3; } - @property int height() { return 30; } - } - - Duck d1 = new Duck(); - Human h1 = new Human(); - HumanStructure hs1; - - interface Refreshable - { - int refresh(); - } - // does not have structural conformance - static assert(!__traits(compiles, d1.wrap!Refreshable)); - static assert(!__traits(compiles, h1.wrap!Refreshable)); - static assert(!__traits(compiles, hs1.wrap!Refreshable)); - - // strict upcast - Quack qd = d1.wrap!Quack; - assert(qd is d1); - assert(qd.quack() == 1); // calls Duck.quack - // strict downcast - Duck d2 = qd.unwrap!Duck; - assert(d2 is d1); - - // structural upcast - Quack qh = h1.wrap!Quack; - Quack qhs = hs1.wrap!Quack; - assert(qh.quack() == 2); // calls Human.quack - assert(qhs.quack() == 3); // calls HumanStructure.quack - // structural downcast - Human h2 = qh.unwrap!Human; - HumanStructure hs2 = qhs.unwrap!HumanStructure; - assert(h2 is h1); - assert(hs2 is hs1); - - // structural upcast (two steps) - Quack qx = h1.wrap!Quack; // Human -> Quack - Quack qxs = hs1.wrap!Quack; // HumanStructure -> Quack - Flyer fx = qx.wrap!Flyer; // Quack -> Flyer - Flyer fxs = qxs.wrap!Flyer; // Quack -> Flyer - assert(fx.height == 20); // calls Human.height - assert(fxs.height == 30); // calls HumanStructure.height - // strucural downcast (two steps) - Quack qy = fx.unwrap!Quack; // Flyer -> Quack - Quack qys = fxs.unwrap!Quack; // Flyer -> Quack - Human hy = qy.unwrap!Human; // Quack -> Human - HumanStructure hys = qys.unwrap!HumanStructure; // Quack -> HumanStructure - assert(hy is h1); - assert(hys is hs1); - // strucural downcast (one step) - Human hz = fx.unwrap!Human; // Flyer -> Human - HumanStructure hzs = fxs.unwrap!HumanStructure; // Flyer -> HumanStructure - assert(hz is h1); - assert(hzs is hs1); -} - -/// -@system unittest -{ - import std.traits : functionAttributes, FunctionAttribute; - interface A { int run(); } - interface B { int stop(); @property int status(); } - class X - { - int run() { return 1; } - int stop() { return 2; } - @property int status() { return 3; } - } - - auto x = new X(); - auto ab = x.wrap!(A, B); - A a = ab; - B b = ab; - assert(a.run() == 1); - assert(b.stop() == 2); - assert(b.status == 3); - static assert(functionAttributes!(typeof(ab).status) & FunctionAttribute.property); -} - -template unwrap(Target) -{ - static if (!isMutable!Target) - alias unwrap = .unwrap!(Unqual!Target); - else - { - // strict downcast - auto unwrap(Source)(inout Source src) - if (is(Target : Source)) - { - alias T = Select!(is(Source == shared), shared Target, Target); - return dynamicCast!(inout T)(src); - } - - // structural downcast for struct target - auto unwrap(Source)(inout Source src) - if (is(Target == struct)) - { - alias T = Select!(is(Source == shared), shared Target, Target); - auto upCastSource = dynamicCast!Object(src); // remove qualifier - do - { - if (auto a = dynamicCast!(Structural!Object)(upCastSource)) - { - upCastSource = a._wrap_getSource(); - } - else if (auto a = dynamicCast!(Structural!T)(upCastSource)) - { - return a._wrap_getSource(); - } - else - { - static if (hasMember!(Source, "_wrap_getSource")) - return unwrap!Target(src._wrap_getSource()); - else - break; - } - } while (upCastSource); - import std.conv : ConvException; - throw new ConvException(unwrapExceptionText!(Source,Target)); - } - // structural downcast for class target - auto unwrap(Source)(inout Source src) - if (!is(Target : Source) && !is(Target == struct)) - { - alias T = Select!(is(Source == shared), shared Target, Target); - Object upCastSource = dynamicCast!(Object)(src); // remove qualifier - do - { - // Unwrap classes - if (auto a = dynamicCast!(Structural!Object)(upCastSource)) - { - if (auto d = dynamicCast!(inout T)(upCastSource = a._wrap_getSource())) - return d; - } - // Unwrap a structure of type T - else if (auto a = dynamicCast!(Structural!T)(upCastSource)) - { - return a._wrap_getSource(); - } - // Unwrap class that already inherited from interface - else if (auto d = dynamicCast!(inout T)(upCastSource)) - { - return d; - } - // Recurse to find the struct Target within a wrapped tree - else - { - static if (hasMember!(Source, "_wrap_getSource")) - return unwrap!Target(src._wrap_getSource()); - else - break; - } - } while (upCastSource); - return null; - } - } -} - -@system unittest -{ - // Validate const/immutable - class A - { - int draw() { return 1; } - int draw(int v) { return v; } - - int draw() const { return 2; } - int draw() shared { return 3; } - int draw() shared const { return 4; } - int draw() immutable { return 5; } - } - interface Drawable - { - int draw(); - int draw() const; - int draw() shared; - int draw() shared const; - int draw() immutable; - } - interface Drawable2 - { - int draw(int v); - } - - auto ma = new A(); - auto sa = new shared A(); - auto ia = new immutable A(); - { - Drawable md = ma.wrap!Drawable; - const Drawable cd = ma.wrap!Drawable; - shared Drawable sd = sa.wrap!Drawable; - shared const Drawable scd = sa.wrap!Drawable; - immutable Drawable id = ia.wrap!Drawable; - assert( md.draw() == 1); - assert( cd.draw() == 2); - assert( sd.draw() == 3); - assert(scd.draw() == 4); - assert( id.draw() == 5); - } - { - Drawable2 d = ma.wrap!Drawable2; - static assert(!__traits(compiles, d.draw())); - assert(d.draw(10) == 10); - } -} - -// https://issues.dlang.org/show_bug.cgi?id=10377 -@system unittest -{ - import std.algorithm, std.range; - - interface MyInputRange(T) - { - @property T front(); - void popFront(); - @property bool empty(); - } - - //auto o = iota(0,10,1).inputRangeObject(); - //pragma(msg, __traits(allMembers, typeof(o))); - auto r = iota(0,10,1).inputRangeObject().wrap!(MyInputRange!int)(); - assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); -} - -// https://issues.dlang.org/show_bug.cgi?id=10536 -@system unittest -{ - interface Interface - { - int foo(); - } - class Pluggable - { - int foo() { return 1; } - @disable void opCast(T, this X)(); // ! - } - - Interface i = new Pluggable().wrap!Interface; - assert(i.foo() == 1); -} - -// https://issues.dlang.org/show_bug.cgi?id=10538 -@system unittest -{ - interface Interface - { - int foo(); - int bar(int); - } - class Pluggable - { - int opDispatch(string name, A...)(A args) { return 100; } - } - - Interface i = wrap!Interface(new Pluggable()); - assert(i.foo() == 100); - assert(i.bar(10) == 100); -} - -// Concat all Targets function members into one tuple -private template ConcatInterfaceMembers(Targets...) -{ - static if (Targets.length == 0) - alias ConcatInterfaceMembers = AliasSeq!(); - else static if (Targets.length == 1) - alias ConcatInterfaceMembers - = AliasSeq!(GetOverloadedMethods!(Targets[0])); - else - alias ConcatInterfaceMembers = AliasSeq!( - GetOverloadedMethods!(Targets[0]), - ConcatInterfaceMembers!(Targets[1..$])); -} -// Remove duplicated functions based on the identifier name and function type covariance -private template UniqMembers(members...) -{ - template FuncInfo(string s, F) - { - enum name = s; - alias type = F; - } - - static if (members.length == 0) - alias UniqMembers = AliasSeq!(); - else - { - alias func = members[0]; - enum name = __traits(identifier, func); - alias type = FunctionTypeOf!func; - template check(size_t i, mem...) - { - static if (i >= mem.length) - enum ptrdiff_t check = -1; - else static if - (__traits(identifier, func) == __traits(identifier, mem[i]) && - !is(DerivedFunctionType!(type, FunctionTypeOf!(mem[i])) == void)) - { - enum ptrdiff_t check = i; - } - else - enum ptrdiff_t check = check!(i + 1, mem); - } - enum ptrdiff_t x = 1 + check!(0, members[1 .. $]); - static if (x >= 1) - { - alias typex = DerivedFunctionType!(type, FunctionTypeOf!(members[x])); - alias remain = UniqMembers!(members[1 .. x], members[x + 1 .. $]); - - static if (remain.length >= 1 && remain[0].name == name && - !is(DerivedFunctionType!(typex, remain[0].type) == void)) - { - alias F = DerivedFunctionType!(typex, remain[0].type); - alias UniqMembers = AliasSeq!(FuncInfo!(name, F), remain[1 .. $]); - } - else - alias UniqMembers = AliasSeq!(FuncInfo!(name, typex), remain); - } - else - { - alias UniqMembers = AliasSeq!(FuncInfo!(name, type), UniqMembers!(members[1 .. $])); - } - } -} - -// find a function from Fs that has same identifier and covariant type with f -private template findCovariantFunction(alias finfo, Source, Fs...) -{ - template check(size_t i = 0) - { - static if (i >= Fs.length) - enum ptrdiff_t check = -1; - else - { - enum ptrdiff_t check = - (finfo.name == __traits(identifier, Fs[i])) && - isCovariantWith!(FunctionTypeOf!(Fs[i]), finfo.type) - ? i : check!(i + 1); - } - } - enum x = check!(); - static if (x == -1 && is(typeof(Source.opDispatch))) - { - alias Params = Parameters!(finfo.type); - enum ptrdiff_t findCovariantFunction = - is(typeof(( Source).init.opDispatch!(finfo.name)(Params.init))) || - is(typeof(( const Source).init.opDispatch!(finfo.name)(Params.init))) || - is(typeof(( immutable Source).init.opDispatch!(finfo.name)(Params.init))) || - is(typeof(( shared Source).init.opDispatch!(finfo.name)(Params.init))) || - is(typeof((shared const Source).init.opDispatch!(finfo.name)(Params.init))) - ? ptrdiff_t.max : -1; - } - else - enum ptrdiff_t findCovariantFunction = x; -} - -/** -Type constructor for final (aka head-const) variables. - -Final variables cannot be directly mutated or rebound, but references -reached through the variable are typed with their original mutability. -It is equivalent to `final` variables in D1 and Java, as well as -`readonly` variables in C#. - -When `T` is a `const` or `immutable` type, `Final` aliases -to `T`. -*/ -template Final(T) -{ -static if (is(T == const) || is(T == immutable)) - alias Final = T; -else -{ - struct Final - { - import std.typecons : Proxy; - - private T final_value; - mixin Proxy!final_value; - - /** - * Construction is forwarded to the underlying type. - */ - this(T other) - { - this.final_value = other; - } - - /// Ditto - this(Args...)(auto ref Args args) - if (__traits(compiles, T(args))) - { - static assert((!is(T == struct) && !is(T == union)) || !isNested!T, - "Non-static nested type " ~ fullyQualifiedName!T ~ " must be " ~ - "constructed explicitly at the call-site (e.g. auto s = " ~ - "makeFinal(" ~ T.stringof ~ "(...));)"); - this.final_value = T(args); - } - - // Attaching function attributes gives less noisy error messages - pure nothrow @safe @nogc - { - /++ - + All operators, including member access, are forwarded to the - + underlying value of type `T` except for these mutating operators, - + which are disabled. - +/ - void opAssign(Other)(Other other) - { - static assert(0, typeof(this).stringof ~ - " cannot be reassigned."); - } - - /// Ditto - void opOpAssign(string op, Other)(Other other) - { - static assert(0, typeof(this).stringof ~ - " cannot be reassigned."); - } - - /// Ditto - void opUnary(string op : "--")() - { - static assert(0, typeof(this).stringof ~ - " cannot be mutated."); - } - - /// Ditto - void opUnary(string op : "++")() - { - static assert(0, typeof(this).stringof ~ - " cannot be mutated."); - } - } - - /** - * - * `Final!T` implicitly converts to an rvalue of type `T` through - * `AliasThis`. - */ - inout(T) final_get() inout - { - return final_value; - } - - /// Ditto - alias final_get this; - - /// Ditto - auto ref opUnary(string op)() - if (__traits(compiles, mixin(op ~ "T.init"))) - { - return mixin(op ~ "this.final_value"); - } - } -} -} - -/// Ditto -Final!T makeFinal(T)(T t) -{ - return Final!T(t); -} - -/// `Final` can be used to create class references which cannot be rebound: -pure nothrow @safe unittest -{ - static class A - { - int i; - - this(int i) pure nothrow @nogc @safe - { - this.i = i; - } - } - - auto a = makeFinal(new A(42)); - assert(a.i == 42); - - //a = new A(24); // Reassignment is illegal, - a.i = 24; // But fields are still mutable. - - assert(a.i == 24); -} - -/// `Final` can also be used to create read-only data fields without using transitive immutability: -pure nothrow @safe unittest -{ - static class A - { - int i; - - this(int i) pure nothrow @nogc @safe - { - this.i = i; - } - } - - static class B - { - Final!A a; - - this(A a) pure nothrow @nogc @safe - { - this.a = a; // Construction, thus allowed. - } - } - - auto b = new B(new A(42)); - assert(b.a.i == 42); - - // b.a = new A(24); // Reassignment is illegal, - b.a.i = 24; // but `a` is still mutable. - - assert(b.a.i == 24); -} - -pure nothrow @safe unittest -{ - static class A { int i; } - static assert(!is(Final!A == A)); - static assert(is(Final!(const A) == const A)); - static assert(is(Final!(immutable A) == immutable A)); - - Final!A a = new A; - static assert(!__traits(compiles, a = new A)); - - assert(a.i == 0); - a.i = 42; - assert(a.i == 42); - - Final!int i = 42; - static assert(!__traits(compiles, i = 24)); - static assert(!__traits(compiles, --i)); - static assert(!__traits(compiles, ++i)); - assert(i == 42); - int iCopy = i; - assert(iCopy == 42); - iCopy = -i; // non-mutating unary operators must work - assert(iCopy == -42); - - static struct S - { - int i; - - pure nothrow @safe @nogc: - this(int i){} - this(string s){} - this(int i, string s, float f){ this.i = i; } - } - - Final!S sint = 42; - Final!S sstr = "foo"; - static assert(!__traits(compiles, sint = sstr)); - - auto sboth = Final!S(42, "foo", 3.14); - assert(sboth.i == 42); - - sboth.i = 24; - assert(sboth.i == 24); - - struct NestedS - { - int i; - int get() pure nothrow @safe @nogc { return sboth.i + i; } - } - - // Nested structs must be constructed at the call-site - static assert(!__traits(compiles, Final!NestedS(6))); - auto s = makeFinal(NestedS(6)); - assert(s.i == 6); - assert(s.get == 30); - - class NestedC - { - int i; - - pure nothrow @safe @nogc: - this(int i) { this.i = i; } - int get() { return sboth.i + i; } - } - - auto c = makeFinal(new NestedC(6)); - assert(c.i == 6); - assert(c.get == 30); -} - -pure nothrow @safe unittest -{ - auto arr = makeFinal([1, 2, 3]); - static assert(!__traits(compiles, arr = null)); - static assert(!__traits(compiles, arr ~= 4)); - assert((arr ~ 4) == [1, 2, 3, 4]); -} - -// https://issues.dlang.org/show_bug.cgi?id=17270 -pure nothrow @nogc @system unittest -{ - int i = 1; - Final!(int*) fp = &i; - assert(*fp == 1); - static assert(!__traits(compiles, - fp = &i // direct assignment - )); - static assert(is(typeof(*fp) == int)); - *fp = 2; // indirect assignment - assert(*fp == 2); - int* p = fp; - assert(*p == 2); -} - -pure nothrow @system unittest -{ - Final!(int[]) arr; - // static assert(!__traits(compiles, - // arr.length = 10; // bug! - // )); - static assert(!__traits(compiles, - arr.ptr = null - )); - static assert(!__traits(compiles, - arr.ptr++ - )); -} diff --git a/std/file.d b/std/file.d index b3dc4e8d1a5..88755dc7b0c 100644 --- a/std/file.d +++ b/std/file.d @@ -3714,7 +3714,7 @@ assert(!de2.isFile); @property bool isSymlink() scope; /++ - Returns the size of the the file represented by this `DirEntry` + Returns the size of the file represented by this `DirEntry` in bytes. +/ @property ulong size() scope; @@ -4465,9 +4465,10 @@ void rmdirRecurse(ref scope DirEntry de) @safe } else { - // dirEntries is @system because it uses a DirIterator with a - // RefCounted variable, but here, no references to the payload is - // escaped to the outside, so this should be @trusted + // dirEntries is @system without DIP1000 because it uses + // a DirIterator with a SafeRefCounted variable, but here, no + // references to the payload are escaped to the outside, so this should + // be @trusted () @trusted { // all children, recursively depth-first foreach (DirEntry e; dirEntries(de.name, SpanMode.depth, false)) @@ -4866,21 +4867,32 @@ private struct DirIteratorImpl(alias pred = (const scope ref DirEntry entry) => } } -struct DirIterator(alias pred = (const scope ref DirEntry entry) => true) +struct _DirIterator(bool useDIP1000, alias pred = (const scope ref DirEntry entry) => true) // TODO: if (is(typeof(pred(DirEntry.init)) : bool)) +// Must be a template, because the destructor is unsafe or safe depending on +// whether `-preview=dip1000` is in use. Otherwise, linking errors would +// result. { -@safe: + static assert(useDIP1000 == dip1000Enabled, + "Please don't override useDIP1000 to disagree with compiler switch."); + private: - RefCounted!(DirIteratorImpl!(pred), RefCountedAutoInitialize.no) impl; + SafeRefCounted!(DirIteratorImpl!(pred), RefCountedAutoInitialize.no) impl; + this(string pathname, SpanMode mode, bool followSymlink) @trusted { impl = typeof(impl)(pathname, mode, followSymlink); } public: - @property bool empty() { return impl.empty; } - @property DirEntry front() { return impl.front; } - void popFront() { impl.popFront(); } + @property bool empty() @trusted { return impl.empty; } + @property DirEntry front() @trusted { return impl.front; } + void popFront() @trusted { impl.popFront(); } } + +// This has the client code to automatically use and link to the correct +// template instance +alias DirIterator = _DirIterator!dip1000Enabled; + /++ Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of `DirEntry` that lazily iterates a given directory, @@ -4894,6 +4906,11 @@ public: operating system / filesystem, and may not follow any particular sorting. Params: + useDIP1000 = used to instantiate this function separately for code with + and without -preview=dip1000 compiler switch, because it + affects the ABI of this function. Set automatically - + don't touch. + path = The directory to iterate over. If empty, the current directory will be iterated. @@ -4959,14 +4976,14 @@ foreach (d; dFiles) writeln(d.name); -------------------- +/ -auto dirEntries(string path, SpanMode mode, bool followSymlink = true) -{ - return DirIterator!()(path, mode, followSymlink); -} -auto dirEntries(alias pred)(string path, SpanMode mode, bool followSymlink = true) + +// For some reason, doing the same alias-to-a-template trick as with DirIterator +// does not work here. +auto dirEntries(bool useDIP1000 = dip1000Enabled, alias pred = (const scope ref DirEntry entry) => true) + (string path, SpanMode mode, bool followSymlink = true) // TODO: if (is(typeof(pred(DirEntry.init)) : bool)) { - return DirIterator!(pred)(path, mode, followSymlink); + return _DirIterator!(useDIP1000, pred)(path, mode, followSymlink); } /// Duplicate functionality of D1's `std.file.listdir()`: @@ -4985,13 +5002,14 @@ auto dirEntries(alias pred)(string path, SpanMode mode, bool followSymlink = tru .array; } - void main(string[] args) + // Can be safe only with -preview=dip1000 + @safe void main(string[] args) { import std.stdio; string[] files = listdir(args[1]); writefln("%s", files); - } + } } @system unittest @@ -5066,14 +5084,15 @@ auto dirEntries(alias pred)(string path, SpanMode mode, bool followSymlink = tru } /// Ditto -auto dirEntries(string path, string pattern, SpanMode mode, +auto dirEntries(bool useDIP1000 = dip1000Enabled) + (string path, string pattern, SpanMode mode, bool followSymlink = true) { import std.algorithm.iteration : filter; import std.path : globMatch, baseName; bool f(DirEntry de) { return globMatch(baseName(de.name), pattern); } - return filter!f(DirIterator!()(path, mode, followSymlink)); + return filter!f(_DirIterator!(useDIP1000)(path, mode, followSymlink)); } @safe unittest @@ -5154,7 +5173,7 @@ auto dirEntries(string path, string pattern, SpanMode mode, // Make sure that dirEntries does not butcher Unicode file names // https://issues.dlang.org/show_bug.cgi?id=17962 -@system unittest +@safe unittest { import std.algorithm.comparison : equal; import std.algorithm.iteration : map; diff --git a/std/format/package.d b/std/format/package.d index 3f6f33adf6e..d83f0281e59 100644 --- a/std/format/package.d +++ b/std/format/package.d @@ -550,7 +550,7 @@ License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com, Andrei Alexandrescu), and Kenji Hara -Source: $(PHOBOSSRC std/format.d) +Source: $(PHOBOSSRC std/format/package.d) */ module std.format; diff --git a/std/internal/math/gammafunction.d b/std/internal/math/gammafunction.d index 7f72234d640..703ecb1e6b3 100644 --- a/std/internal/math/gammafunction.d +++ b/std/internal/math/gammafunction.d @@ -631,7 +631,7 @@ enum real BETA_BIGINV = 1.084202172485504434007e-19L; * betaIncomplete(a, b, x) = Γ(a+b)/(Γ(a) Γ(b)) * * $(INTEGRATE 0, x) $(POWER t, a-1)$(POWER (1-t),b-1) dt * - * and is the same as the the cumulative distribution function. + * and is the same as the cumulative distribution function. * * The domain of definition is 0 <= x <= 1. In this * implementation a and b are restricted to positive values. diff --git a/std/logger/core.d b/std/logger/core.d index be2bd8d175e..846f6ee214a 100644 --- a/std/logger/core.d +++ b/std/logger/core.d @@ -704,7 +704,7 @@ abstract class Logger /** This template provides the log functions for the `Logger` `class` with the `LogLevel` encoded in the function name. - For further information see the the two functions defined inside of this + For further information see the two functions defined inside of this template. The aliases following this template create the public names of these log @@ -1446,7 +1446,7 @@ that the returned reference is only a current snapshot and in the following code, you must make sure no other thread reassigns to it between reading and writing `sharedLog`. -`sharedLog` is only thread-safe if the the used `Logger` is thread-safe. +`sharedLog` is only thread-safe if the used `Logger` is thread-safe. The default `Logger` is thread-safe. ------------- if (sharedLog !is myLogger) @@ -1559,10 +1559,21 @@ class StdForwardLogger : Logger } } + auto oldSharedLog = sharedLog; + sharedLog = new shared RaceLogger; - scope(exit) { sharedLog = null; } - () @trusted { new Thread(() { log("foo"); }).start(); }(); + scope(exit) + { + sharedLog = oldSharedLog; + } + Thread toWaitFor; + () @trusted { toWaitFor = new Thread(() { log("foo"); }).start(); }(); log("bar"); + + () @trusted + { + toWaitFor.join(); + }(); } /** This `LogLevel` is unqiue to every thread. @@ -1897,7 +1908,7 @@ version (StdUnittest) private void testFuncNames(Logger logger) @safe assertThrown!Throwable(logf(LogLevel.fatal, msg, "Yet")); } (); lineNumber = __LINE__ - 2; - assert(l.msg == msg.format("Yet")); + assert(l.msg == msg.format("Yet"), l.msg); assert(l.line == lineNumber); assert(l.logLevel == LogLevel.all); diff --git a/std/net/curl.d b/std/net/curl.d index 7ea2cebb30f..8745bbd7a07 100644 --- a/std/net/curl.d +++ b/std/net/curl.d @@ -28,7 +28,7 @@ to your $(B dub.json) file if you are using $(LINK2 http://code.dlang.org, DUB). Windows x86 note: A DMD compatible libcurl static library can be downloaded from the dlang.org -$(LINK2 http://downloads.dlang.org/other/index.html, download archive page). +$(LINK2 https://downloads.dlang.org/other/index.html, download archive page). This module is not available for iOS, tvOS or watchOS. @@ -1973,7 +1973,7 @@ private mixin template Protocol() } /** - * The network interface to use in form of the the IP of the interface. + * The network interface to use in form of the IP of the interface. * * Example: * ---- @@ -2706,7 +2706,7 @@ struct HTTP @property void dnsTimeout(Duration d); /** - * The network interface to use in form of the the IP of the interface. + * The network interface to use in form of the IP of the interface. * * Example: * ---- @@ -3486,7 +3486,7 @@ struct FTP @property void dnsTimeout(Duration d); /** - * The network interface to use in form of the the IP of the interface. + * The network interface to use in form of the IP of the interface. * * Example: * ---- @@ -3912,7 +3912,7 @@ struct SMTP @property void dnsTimeout(Duration d); /** - * The network interface to use in form of the the IP of the interface. + * The network interface to use in form of the IP of the interface. * * Example: * ---- diff --git a/std/numeric.d b/std/numeric.d index 96d20c233d8..df7ac39b4e4 100644 --- a/std/numeric.d +++ b/std/numeric.d @@ -1124,8 +1124,8 @@ public: * * References: "On Enclosing Simple Roots of Nonlinear Equations", * G. Alefeld, F.A. Potra, Yixun Shi, Mathematics of Computation 61, - * pp733-744 (1993). Fortran code available from $(HTTP - * www.netlib.org,www.netlib.org) as algorithm TOMS478. + * pp733-744 (1993). Fortran code available from + * $(HTTP www.netlib.org,www.netlib.org) as algorithm TOMS478. * */ T findRoot(T, DF, DT)(scope DF f, const T a, const T b, diff --git a/std/path.d b/std/path.d index de180fcc548..4b5a7efe07f 100644 --- a/std/path.d +++ b/std/path.d @@ -3357,8 +3357,10 @@ in { // Verify that pattern[] is valid import std.algorithm.searching : balancedParens; - assert(balancedParens(pattern, '[', ']', 0)); - assert(balancedParens(pattern, '{', '}', 0)); + import std.utf : byUTF; + + assert(balancedParens(pattern.byUTF!C, '[', ']', 0)); + assert(balancedParens(pattern.byUTF!C, '{', '}', 0)); } do { @@ -3959,7 +3961,7 @@ string expandTilde(string inputPath) @safe nothrow version (Posix) { import core.exception : onOutOfMemoryError; - import core.stdc.errno : errno, ERANGE; + import core.stdc.errno : errno, EBADF, ENOENT, EPERM, ERANGE, ESRCH; import core.stdc.stdlib : malloc, free, realloc; /* Joins a path from a C string to the remainder of path. @@ -4065,7 +4067,7 @@ string expandTilde(string inputPath) @safe nothrow char[] extra_memory; passwd result; - while (1) + loop: while (1) { extra_memory.length += extra_memory_size; @@ -4088,10 +4090,23 @@ string expandTilde(string inputPath) @safe nothrow break; } - if (errno != ERANGE && + switch (errno) + { + case ERANGE: // On BSD and OSX, errno can be left at 0 instead of set to ERANGE - errno != 0) - onOutOfMemoryError(); + case 0: + break; + + case ENOENT: + case ESRCH: + case EBADF: + case EPERM: + // The given name or uid was not found. + break loop; + + default: + onOutOfMemoryError(); + } // extra_memory isn't large enough import core.checkedint : mulu; diff --git a/std/range/primitives.d b/std/range/primitives.d index 31f58fa5fa9..cdab401a1f2 100644 --- a/std/range/primitives.d +++ b/std/range/primitives.d @@ -172,7 +172,7 @@ Returns: enum bool isInputRange(R) = is(typeof(R.init) == R) && is(ReturnType!((R r) => r.empty) == bool) - && is(typeof((return ref R r) => r.front)) + && (is(typeof((return ref R r) => r.front)) || is(typeof(ref (return ref R r) => r.front))) && !is(ReturnType!((R r) => r.front) == void) && is(typeof((R r) => r.popFront)); @@ -227,6 +227,17 @@ enum bool isInputRange(R) = } static assert(!isInputRange!VoidFront); } +// https://issues.dlang.org/show_bug.cgi?id=16034 +@safe unittest +{ + struct One + { + int entry = 1; + @disable this(this); + } + + assert(isInputRange!(One[])); +} @safe unittest { diff --git a/std/socket.d b/std/socket.d index 6ec79858194..fb2c2d42253 100644 --- a/std/socket.d +++ b/std/socket.d @@ -2942,7 +2942,7 @@ public: * Calling `shutdown` before `close` is recommended * for connection-oriented sockets. */ - void close() @trusted nothrow @nogc + void close() scope @trusted nothrow @nogc { _close(sock); sock = socket_t.init; @@ -3641,7 +3641,7 @@ class UdpSocket: Socket { checkAttributes!q{nothrow @nogc @trusted}; } - nothrow @nogc @trusted void close() + nothrow @nogc @trusted scope void close() { checkAttributes!q{nothrow @nogc @trusted}; } diff --git a/std/stdio.d b/std/stdio.d index a1fe962906b..fea9b4c8d58 100644 --- a/std/stdio.d +++ b/std/stdio.d @@ -85,8 +85,7 @@ else version (CRuntime_Musl) } else version (CRuntime_UClibc) { - // uClibc supports GCC IO - version = GCC_IO; + version = GENERIC_IO; } else version (OSX) { @@ -589,7 +588,7 @@ Throws: `ErrnoException` if the file could not be opened. detach(); } - this(this) @safe nothrow + this(this) @safe pure nothrow @nogc { if (!_p) return; assert(atomicLoad(_p.refs)); @@ -5211,7 +5210,7 @@ enum StdFileHandle: string { with (StdFileHandle) assert(_iob == stdin || _iob == stdout || _iob == stderr); - impl.handle = mixin(_iob); + impl.handle = cast() mixin(_iob); result._p = &impl; atomicOp!"+="(spinlock, uint.max / 2); break; diff --git a/std/string.d b/std/string.d index 5c9a2c946ba..7ed24f7f98d 100644 --- a/std/string.d +++ b/std/string.d @@ -6884,7 +6884,7 @@ if (isSomeString!S) if (inword) { - if (col + 1 + (s.length - wordstart) >= columns) + if (col + 1 + (s.length - wordstart) > columns) { result ~= '\n'; result ~= indent; @@ -6929,6 +6929,13 @@ if (isSomeString!S) }); } +@safe pure unittest // https://issues.dlang.org/show_bug.cgi?id=23298 +{ + assert("1 2 3 4 5 6 7 8 9".wrap(17) == "1 2 3 4 5 6 7 8 9\n"); + assert("1 2 3 4 5 6 7 8 9 ".wrap(17) == "1 2 3 4 5 6 7 8 9\n"); + assert("1 2 3 4 5 6 7 8 99".wrap(17) == "1 2 3 4 5 6 7 8\n99\n"); +} + /****************************************** * Removes one level of indentation from a multi-line string. * diff --git a/std/sumtype.d b/std/sumtype.d index 160665c6a31..ee2d73a1233 100644 --- a/std/sumtype.d +++ b/std/sumtype.d @@ -753,23 +753,6 @@ public: } } - invariant - { - this.match!((ref value) { - static if (is(typeof(value) == class)) - { - if (value !is null) - { - assert(value); - } - } - else static if (is(typeof(value) == struct)) - { - assert(&value); - } - }); - } - // Workaround for https://issues.dlang.org/show_bug.cgi?id=21400 version (StdDdoc) { @@ -1330,36 +1313,6 @@ version (D_BetterC) {} else })); } -// Types with invariants -// Disabled in BetterC due to use of exceptions -version (D_BetterC) {} else -version (D_Invariants) -@system unittest -{ - import std.exception : assertThrown; - import core.exception : AssertError; - - struct S - { - int i; - invariant { assert(i >= 0); } - } - - class C - { - int i; - invariant { assert(i >= 0); } - } - - SumType!S x; - x.match!((ref v) { v.i = -1; }); - assertThrown!AssertError(assert(&x)); - - SumType!C y = new C(); - y.match!((ref v) { v.i = -1; }); - assertThrown!AssertError(assert(&y)); -} - // Calls value postblit on self-assignment @safe unittest { diff --git a/std/traits.d b/std/traits.d index cc08ea638be..1cbff8414e3 100644 --- a/std/traits.d +++ b/std/traits.d @@ -9136,3 +9136,11 @@ package(std) template DeducedParameterType(T) { static assert(is(DeducedParameterType!(inout(int[])) == inout(int)[])); } + +private auto dip1000Test(int x) {return *&x;} +// We don't use isSafe, because betterC client code needs to instantiate +// core.internal.array.comparison.__cmp in the client side. isSafe uses +// __cmp of two strings, so using it would instantate that here instead. That +// won't do because betterC compilations do not link the Phobos binary in. +package(std) enum dip1000Enabled + = is(typeof(&dip1000Test) : int function(int) @safe); diff --git a/std/typecons.d b/std/typecons.d index c7cdc24e4d5..5b60679bb02 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -22,8 +22,8 @@ $(TR $(TD Flags) $(TD $(LREF Yes) )) $(TR $(TD Memory allocation) $(TD - $(LREF RefCounted) - $(LREF refCounted) + $(LREF SafeRefCounted) + $(LREF safeRefCounted) $(LREF RefCountedAutoInitialize) $(LREF scoped) $(LREF Unique) @@ -2759,6 +2759,9 @@ Nullable!T) object starts in the null state. Assigning it renders it non-null. Calling `nullify` can nullify it again. Practically `Nullable!T` stores a `T` and a `bool`. + +See also: + $(LREF apply), an alternative way to use the payload. */ struct Nullable(T) { @@ -3793,8 +3796,28 @@ Params: sink.formatValue(_value, fmt); } } + + void toString()(scope void delegate(const(char)[]) sink, scope const ref FormatSpec!char fmt) const + { + if (isNull) + { + sink.formatValue("Nullable.null", fmt); + } + else + { + sink.formatValue(_value, fmt); + } + } } +@system unittest +{ + import std.conv : to; + + const Nullable!(ulong, 0) x = 1; + assert(x.to!string == "1"); +} + /** Check if `this` is in the null state. @@ -4158,7 +4181,7 @@ Returns: See also: $(HTTPS en.wikipedia.org/wiki/Monad_(functional_programming)#The_Maybe_monad, The `Maybe` monad) - */ +*/ template apply(alias fun) { import std.functional : unaryFun; @@ -4320,8 +4343,28 @@ Params: sink.formatValue(*_value, fmt); } } + + void toString()(scope void delegate(const(char)[]) sink, scope const ref FormatSpec!char fmt) const + { + if (isNull) + { + sink.formatValue("Nullable.null", fmt); + } + else + { + sink.formatValue(*_value, fmt); + } + } } +@system unittest +{ + import std.conv : to; + + const NullableRef!(ulong) x = new ulong(1); + assert(x.to!string == "1"); +} + /** Binds the internal state to `value`. @@ -6510,8 +6553,8 @@ package template Bind(alias Template, args1...) /** -Options regarding auto-initialization of a `RefCounted` object (see -the definition of `RefCounted` below). +Options regarding auto-initialization of a `SafeRefCounted` object (see +the definition of `SafeRefCounted` below). */ enum RefCountedAutoInitialize { @@ -6532,6 +6575,27 @@ enum RefCountedAutoInitialize int a = 42; } + SafeRefCounted!(Foo, RefCountedAutoInitialize.yes) rcAuto; + SafeRefCounted!(Foo, RefCountedAutoInitialize.no) rcNoAuto; + + assert(rcAuto.refCountedPayload.a == 42); + + assertThrown!AssertError(rcNoAuto.refCountedPayload); + rcNoAuto.refCountedStore.ensureInitialized; + assert(rcNoAuto.refCountedPayload.a == 42); +} + +// Same the above but for old RefCounted and not documented +@system unittest +{ + import core.exception : AssertError; + import std.exception : assertThrown; + + struct Foo + { + int a = 42; + } + RefCounted!(Foo, RefCountedAutoInitialize.yes) rcAuto; RefCounted!(Foo, RefCountedAutoInitialize.no) rcNoAuto; @@ -6546,16 +6610,16 @@ enum RefCountedAutoInitialize Defines a reference-counted object containing a `T` value as payload. -An instance of `RefCounted` is a reference to a structure, +An instance of `SafeRefCounted` is a reference to a structure, which is referred to as the $(I store), or $(I storage implementation struct) in this documentation. The store contains a reference count -and the `T` payload. `RefCounted` uses `malloc` to allocate -the store. As instances of `RefCounted` are copied or go out of +and the `T` payload. `SafeRefCounted` uses `malloc` to allocate +the store. As instances of `SafeRefCounted` are copied or go out of scope, they will automatically increment or decrement the reference -count. When the reference count goes down to zero, `RefCounted` +count. When the reference count goes down to zero, `SafeRefCounted` will call `destroy` against the payload and call `free` to deallocate the store. If the `T` payload contains any references -to GC-allocated memory, then `RefCounted` will add it to the GC memory +to GC-allocated memory, then `SafeRefCounted` will add it to the GC memory that is scanned for pointers, and remove it from GC scanning before `free` is called on the store. @@ -6567,8 +6631,15 @@ still be valid during the destructor call. This allows the `T` to deallocate or clean up any non-GC resources immediately after the reference count has reached zero. -`RefCounted` is unsafe and should be used with care. No references -to the payload should be escaped outside the `RefCounted` object. +Without -preview=dip1000, `SafeRefCounted` is unsafe and should be +used with care. No references to the payload should be escaped outside +the `SafeRefCounted` object. + +With -preview=dip1000, `SafeRefCounted` is safe if it's payload is accessed only +with the $(LREF borrow) function. Scope semantics can also prevent accidental +escaping of `refCountedPayload`, but it's still up to the user to not destroy +the last counted reference while the payload is in use. Due to that, +`refCountedPayload` remains accessible only in `@system` code. The `autoInit` option makes the object ensure the store is automatically initialized. Leaving $(D autoInit == @@ -6581,8 +6652,11 @@ pointer dereference. If `T.this()` is annotated with `@disable` then `autoInit` must be `RefCountedAutoInitialize.no` in order to compile. + +See_Also: + $(LREF RefCounted) */ -struct RefCounted(T, RefCountedAutoInitialize autoInit = +struct SafeRefCounted(T, RefCountedAutoInitialize autoInit = RefCountedAutoInitialize.yes) if (!is(T == class) && !(is(T == interface))) { @@ -6602,7 +6676,20 @@ if (!is(T == class) && !(is(T == interface))) import core.memory : GC; } - /// `RefCounted` storage implementation. + pragma(inline, true) private void checkInit()() + if (autoInit == RefCountedAutoInitialize.yes) + { + _refCounted.ensureInitialized(); + } + + pragma(inline, true) private void checkInit()() inout + if (autoInit == RefCountedAutoInitialize.no) + { + assert(_refCounted.isInitialized, + "Attempted to use an uninitialized payload."); + } + + /// `SafeRefCounted` storage implementation. struct RefCountedStore { private struct Impl @@ -6618,7 +6705,7 @@ if (!is(T == class) && !(is(T == interface))) import core.lifetime : emplace, forward; allocateStore(); - version (D_Exceptions) scope(failure) deallocateStore(); + version (D_Exceptions) scope(failure) () @trusted { deallocateStore(); }(); emplace(&_store._payload, forward!args); _store._count = 1; } @@ -6628,7 +6715,7 @@ if (!is(T == class) && !(is(T == interface))) import std.algorithm.mutation : moveEmplace; allocateStore(); - moveEmplace(source, _store._payload); + () @trusted { moveEmplace(source, _store._payload); }(); _store._count = 1; } @@ -6638,13 +6725,15 @@ if (!is(T == class) && !(is(T == interface))) static if (enableGCScan) { import std.internal.memory : enforceCalloc; - _store = cast(Impl*) enforceCalloc(1, Impl.sizeof); - GC.addRange(&_store._payload, T.sizeof); + auto ptr = enforceCalloc(1, Impl.sizeof); + _store = () @trusted { return cast(Impl*) ptr; }(); + () @trusted { GC.addRange(&_store._payload, T.sizeof); }(); } else { import std.internal.memory : enforceMalloc; - _store = cast(Impl*) enforceMalloc(Impl.sizeof); + auto ptr = enforceMalloc(Impl.sizeof); + _store = () @trusted { return cast(Impl*) ptr; }(); } } @@ -6685,6 +6774,7 @@ if (!is(T == class) && !(is(T == interface))) This function is unavailable if `T.this()` is annotated with `@disable`. */ + @safe pure nothrow void ensureInitialized()() { // By checking for `@disable this()` and failing early we can @@ -6723,7 +6813,7 @@ Postcondition: `refCountedStore.isInitialized` } /// Ditto - this(T val) + this(return scope T val) { _refCounted.move(val); } @@ -6746,18 +6836,30 @@ to deallocate the corresponding resource. */ ~this() { + import std.traits : dip1000Enabled; + + // This prevents the same reference from decrementing the count twice. + scope(exit) _refCounted = _refCounted.init; + if (!_refCounted.isInitialized) return; assert(_refCounted._store._count > 0); - if (--_refCounted._store._count) - return; + if (--_refCounted._store._count) return; // Done, destroy and deallocate .destroy(_refCounted._store._payload); - _refCounted.deallocateStore(); + + static if (dip1000Enabled) + { + () @trusted { _refCounted.deallocateStore(); }(); + } + else _refCounted.deallocateStore(); } /** -Assignment operators - */ +Assignment operators. + +Note: You may not assign a new payload to an uninitialized SafeRefCounted, if +auto initialization is off. Assigning another counted reference is still okay. +*/ void opAssign(typeof(this) rhs) { import std.algorithm.mutation : swap; @@ -6770,14 +6872,7 @@ Assignment operators { import std.algorithm.mutation : move; - static if (autoInit == RefCountedAutoInitialize.yes) - { - _refCounted.ensureInitialized(); - } - else - { - assert(_refCounted.isInitialized); - } + checkInit(); move(rhs, _refCounted._store._payload); } @@ -6789,20 +6884,20 @@ Assignment operators RefCountedAutoInitialize.yes), calls $(D refCountedStore.ensureInitialized). Otherwise, just issues $(D assert(refCountedStore.isInitialized)). Used with $(D alias - refCountedPayload this;), so callers can just use the `RefCounted` + refCountedPayload this;), so callers can just use the `SafeRefCounted` object as a `T`. $(BLUE The first overload exists only if $(D autoInit == RefCountedAutoInitialize.yes).) So if $(D autoInit == RefCountedAutoInitialize.no) or called for a constant or immutable object, then - `refCountedPayload` will also be qualified as safe and nothrow + `refCountedPayload` will also be qualified as nothrow (but will still assert if not initialized). */ - @property @trusted + @property @system ref T refCountedPayload() return; /// ditto - @property nothrow @safe pure @nogc + @property nothrow @system pure @nogc ref inout(T) refCountedPayload() inout return; } else @@ -6810,19 +6905,21 @@ Assignment operators static if (autoInit == RefCountedAutoInitialize.yes) { //Can't use inout here because of potential mutation - @property + @property @system ref T refCountedPayload() return { - _refCounted.ensureInitialized(); + checkInit(); return _refCounted._store._payload; } } - - @property nothrow @safe pure @nogc - ref inout(T) refCountedPayload() inout return + else { - assert(_refCounted.isInitialized, "Attempted to access an uninitialized payload."); - return _refCounted._store._payload; + @property nothrow @system pure @nogc + ref inout(T) refCountedPayload() inout return + { + checkInit(); + return _refCounted._store._payload; + } } } @@ -6858,7 +6955,7 @@ assert(refCountedStore.isInitialized)). { // A pair of an `int` and a `size_t` - the latter being the // reference count - will be dynamically allocated - auto rc1 = RefCounted!int(5); + auto rc1 = SafeRefCounted!int(5); assert(rc1 == 5); // No more allocation, add just one extra reference count auto rc2 = rc1; @@ -6868,46 +6965,66 @@ assert(refCountedStore.isInitialized)). // the pair will be freed when rc1 and rc2 go out of scope } -pure @system unittest +// This test can't be betterC because the test extractor won't see the private +// `initialize` method accessed here +pure @safe nothrow @nogc unittest { - RefCounted!int* p; - { - auto rc1 = RefCounted!int(5); - p = &rc1; - assert(rc1 == 5); - assert(rc1._refCounted._store._count == 1); - auto rc2 = rc1; - assert(rc1._refCounted._store._count == 2); - // Reference semantics - rc2 = 42; - assert(rc1 == 42); - rc2 = rc2; - assert(rc2._refCounted._store._count == 2); - rc1 = rc2; - assert(rc1._refCounted._store._count == 2); - } - assert(p._refCounted._store == null); + auto rc1 = SafeRefCounted!(int, RefCountedAutoInitialize.no)(5); + rc1._refCounted.initialize(); +} - // RefCounted as a member - struct A +pure @system unittest +{ + foreach (MyRefCounted; AliasSeq!(SafeRefCounted, RefCounted)) { - RefCounted!int x; - this(int y) + MyRefCounted!int* p; { - x._refCounted.initialize(y); + auto rc1 = MyRefCounted!int(5); + p = &rc1; + assert(rc1 == 5); + assert(rc1._refCounted._store._count == 1); + auto rc2 = rc1; + assert(rc1._refCounted._store._count == 2); + // Reference semantics + rc2 = 42; + assert(rc1 == 42); + rc2 = rc2; + assert(rc2._refCounted._store._count == 2); + rc1 = rc2; + assert(rc1._refCounted._store._count == 2); } - A copy() + assert(p._refCounted._store == null); + + // [Safe]RefCounted as a member + struct A { - auto another = this; - return another; + MyRefCounted!int x; + this(int y) + { + x._refCounted.initialize(y); + } + A copy() + { + auto another = this; + return another; + } } - } - auto a = A(4); - auto b = a.copy(); - assert(a.x._refCounted._store._count == 2, - "https://issues.dlang.org/show_bug.cgi?id=4356 still unfixed"); + auto a = A(4); + auto b = a.copy(); + assert(a.x._refCounted._store._count == 2, + "https://issues.dlang.org/show_bug.cgi?id=4356 still unfixed"); + } +} + +@betterC pure @safe nothrow @nogc unittest +{ + import std.algorithm.mutation : swap; + + SafeRefCounted!int p1, p2; + swap(p1, p2); } +// Same as above but for old RefCounted and not @safe @betterC pure @system nothrow @nogc unittest { import std.algorithm.mutation : swap; @@ -6928,25 +7045,52 @@ pure @system unittest U u; } + alias SRC = SafeRefCounted!S; +} + +// Same as above but for old RefCounted and not @safe +@betterC @system pure nothrow @nogc unittest +{ + union U { + size_t i; + void* p; + } + + struct S { + U u; + } + alias SRC = RefCounted!S; } // https://issues.dlang.org/show_bug.cgi?id=6436 @betterC @system pure unittest { + import std.meta : AliasSeq; struct S { this(int rval) { assert(rval == 1); } this(ref int lval) { assert(lval == 3); ++lval; } } - auto s1 = RefCounted!S(1); - int lval = 3; - auto s2 = RefCounted!S(lval); - assert(lval == 4); + foreach (MyRefCounted; AliasSeq!(SafeRefCounted, RefCounted)) + { + auto s1 = MyRefCounted!S(1); + int lval = 3; + auto s2 = MyRefCounted!S(lval); + assert(lval == 4); + } } // gc_addRange coverage +@betterC @safe pure unittest +{ + struct S { int* p; } + + auto s = SafeRefCounted!S(null); +} + +// Same as above but for old RefCounted and not @safe @betterC @system pure unittest { struct S { int* p; } @@ -6956,69 +7100,253 @@ pure @system unittest @betterC @system pure nothrow @nogc unittest { - RefCounted!int a; - a = 5; //This should not assert - assert(a == 5); + import std.meta : AliasSeq; + foreach (MyRefCounted; AliasSeq!(SafeRefCounted, RefCounted)) + { + MyRefCounted!int a; + a = 5; //This should not assert + assert(a == 5); - RefCounted!int b; - b = a; //This should not assert either - assert(b == 5); + MyRefCounted!int b; + b = a; //This should not assert either + assert(b == 5); - RefCounted!(int*) c; + MyRefCounted!(int*) c; + } } // https://issues.dlang.org/show_bug.cgi?id=21638 @betterC @system pure nothrow @nogc unittest { + import std.meta : AliasSeq; static struct NoDefaultCtor { @disable this(); this(int x) @nogc nothrow pure { this.x = x; } int x; } - auto rc = RefCounted!(NoDefaultCtor, RefCountedAutoInitialize.no)(5); - assert(rc.x == 5); + + foreach (MyRefCounted; AliasSeq!(SafeRefCounted, RefCounted)) + { + auto rc = MyRefCounted!(NoDefaultCtor, RefCountedAutoInitialize.no)(5); + assert(rc.x == 5); + } } // https://issues.dlang.org/show_bug.cgi?id=20502 @system unittest { - import std.conv : to; - // Check that string conversion is transparent for refcounted - // structs that do not have either toString or alias this. - static struct A { Object a; } - auto a = A(new Object()); - auto r = refCounted(a); - assert(to!string(r) == to!string(a)); - assert(to!string(cast(const) r) == to!string(cast(const) a)); - // Check that string conversion is still transparent for refcounted - // structs that have alias this. - static struct B { int b; alias b this; } - static struct C { B b; alias b this; } - assert(to!string(refCounted(C(B(123)))) == to!string(C(B(123)))); - // https://issues.dlang.org/show_bug.cgi?id=22093 - // Check that uninitialized refcounted structs that previously could be - // converted to strings still can be. - alias R = typeof(r); - R r2; - cast(void) (((const ref R a) => to!string(a))(r2)); - cast(void) to!string(RefCounted!(A, RefCountedAutoInitialize.no).init); + alias Types = AliasSeq!(SafeRefCounted, RefCounted); + alias funcs = AliasSeq!(safeRefCounted, refCounted); + static foreach (aliasI; 0 .. 2) + {{ + alias MyRefCounted = Types[aliasI]; + alias myRefCounted = funcs[aliasI]; + import std.conv : to; + + // Check that string conversion is transparent for refcounted + // structs that do not have either toString or alias this. + static struct A { Object a; } + auto a = A(new Object()); + auto r = myRefCounted(a); + assert(to!string(r) == to!string(a)); + assert(to!string(cast(const) r) == to!string(cast(const) a)); + // Check that string conversion is still transparent for refcounted + // structs that have alias this. + static struct B { int b; alias b this; } + static struct C { B b; alias b this; } + assert(to!string(myRefCounted(C(B(123)))) == to!string(C(B(123)))); + // https://issues.dlang.org/show_bug.cgi?id=22093 + // Check that uninitialized refcounted structs that previously could be + // converted to strings still can be. + alias R = typeof(r); + R r2; + cast(void) (((const ref R a) => to!string(a))(r2)); + cast(void) to!string(MyRefCounted!(A, RefCountedAutoInitialize.no).init); + }} +} + +// We tried to make `refCountedPayload` `@safe` in +// https://github.com/dlang/phobos/pull/8368 . It proved impossible, but it may +// be possible in the future. This test checks for false `@safe` issues we +// encountered. +@safe unittest +{ + struct Container + { + int[] data; + } + + int[] getArr1 (scope Container local) + { + // allowed because the argument is inferred as return scope. + return local.data; + } + + int[] getArr2 (scope Container local) + { + SafeRefCounted!Container rc = local; + // Escapes a reference to expired reference counted struct + // don't do this! + return rc.refCountedPayload().data; + } + + int destroyFirstAndUseLater() + { + auto rc = SafeRefCounted!int(123); + int* ptr = &rc.refCountedPayload(); + destroy(rc); + return *ptr; + } + + // This is here mainly to test that safety gets inferred correctly for the + // next tests + static assert(isSafe!getArr1); + // https://github.com/dlang/phobos/pull/8101#issuecomment-843017282 + // This got apparently fixed automatically by compiler updates. + static assert(!isSafe!getArr2); + // As of writing, this is the issue that is still preventing payload access + // from being `@safe` + static assert(!isSafe!destroyFirstAndUseLater); +} + +/** +Borrows the payload of $(LREF SafeRefCounted) for use in `fun`. Inferred as `@safe` +if `fun` is `@safe` and does not escape a reference to the payload. +The reference count will be incremented for the duration of the operation, +so destroying the last reference will not leave dangling references in +`fun`. + +Params: + fun = A callable accepting the payload either by value or by reference. + refCount = The counted reference to the payload. +Returns: + The return value of `fun`, if any. `ref` in the return value will be + forwarded. +Issues: + For yet unknown reason, code that uses this function with UFCS syntax + will not be inferred as `@safe`. It will still compile if the code is + explicitly marked `@safe` and nothing in `fun` prevents that. +*/ +template borrow(alias fun) +{ + import std.functional : unaryFun; + + auto ref borrow(RC)(RC refCount) if + ( + isInstanceOf!(SafeRefCounted, RC) + && is(typeof(unaryFun!fun(refCount.refCountedPayload))) + ) + { + refCount.checkInit(); + + // If `fun` escapes a reference to the payload, it will be inferred + // as unsafe due to the scope storage class here. + scope plPtr = &refCount._refCounted._store._payload; + return unaryFun!fun(*plPtr); + + // We destroy our copy of the reference here, automatically destroying + // the payload if `fun` destroyed the last reference outside. + } +} + +/// This example can be marked `@safe` with `-preview=dip1000`. +@safe pure nothrow unittest +{ + auto rcInt = safeRefCounted(5); + assert(rcInt.borrow!(theInt => theInt) == 5); + auto sameInt = rcInt; + assert(sameInt.borrow!"a" == 5); + + // using `ref` in the function + auto arr = [0, 1, 2, 3, 4, 5, 6]; + sameInt.borrow!(ref (x) => arr[x]) = 10; + assert(arr == [0, 1, 2, 3, 4, 10, 6]); + + // modifying the payload via an alias + sameInt.borrow!"a*=2"; + assert(rcInt.borrow!"a" == 10); +} + +// Some memory safety penetration testing. +@system unittest +{ + int* globalPtr; + int torpedoesFired = 0; + struct Destroyer { ~this() @safe { torpedoesFired++; } } + + alias RcInt = typeof(safeRefCounted(0)); + auto standardUsage(RcInt arg) + { + return borrow!((ref x) => x)(arg); + } + ref harmlessRefReturn(RcInt arg) + { + return borrow!(ref (ref x) => *globalPtr = x)(arg); + } + ref problematicRefReturn(RcInt arg) + { + return borrow!(ref (ref x) => x)(arg); + } + auto sideChannelEscape(RcInt arg) + { + return borrow!((ref x) + { + globalPtr = &x; + return x; + })(arg); + } + auto destroyDuringApply() + { + auto rc = safeRefCounted(Destroyer()); + return borrow!((ref x) + { + // Destroys the last reference to the payload, decrementing it's + // reference count. + rc.__dtor(); + // Destroy again! rc should be set to it's init value so that this + // won't decrement the reference count of the original payload. + rc.__dtor(); + // The internal reference count increment done for duration of + // `apply` should make sure that the payload destructor is not yet + // run, and this value thus not incremented. + return torpedoesFired; + })(rc); + } + + // First, let's verify the dangerous functions really do what they are + // supposed to do. + auto testRc = safeRefCounted(42); + assert(sideChannelEscape(testRc) == 42); + assert(&problematicRefReturn(testRc) == globalPtr); + + // Now, are the @safe attributes inferred correctly? + assert(isSafe!standardUsage); + assert(isSafe!harmlessRefReturn); + assert(!isSafe!problematicRefReturn); + assert(!isSafe!sideChannelEscape); + assert(isSafe!destroyDuringApply); + + // Finally, we test protection against early destruction during `apply`. + auto torpedoesUpToReturn = destroyDuringApply(); + assert(torpedoesFired == torpedoesUpToReturn + 1); } /** - * Initializes a `RefCounted` with `val`. The template parameter - * `T` of `RefCounted` is inferred from `val`. + * Initializes a `SafeRefCounted` with `val`. The template parameter + * `T` of `SafeRefCounted` is inferred from `val`. * This function can be used to move non-copyable values to the heap. - * It also disables the `autoInit` option of `RefCounted`. + * It also disables the `autoInit` option of `SafeRefCounted`. * * Params: * val = The value to be reference counted * Returns: - * An initialized `RefCounted` containing `val`. + * An initialized `SafeRefCounted` containing `val`. * See_Also: + * $(LREF refCounted) * $(HTTP en.cppreference.com/w/cpp/memory/shared_ptr/make_shared, C++'s make_shared) */ -RefCounted!(T, RefCountedAutoInitialize.no) refCounted(T)(T val) +SafeRefCounted!(T, RefCountedAutoInitialize.no) safeRefCounted(T)(T val) { typeof(return) res; res._refCounted.move(val); @@ -7043,13 +7371,13 @@ RefCounted!(T, RefCountedAutoInitialize.no) refCounted(T)(T val) assert(File.nDestroyed == 0); - // make the file refcounted to share ownership + // make the file ref counted to share ownership // Note: - // We write a compound statement (brace-delimited scope) in which all `RefCounted!File` handles are created and deleted. + // We write a compound statement (brace-delimited scope) in which all `SafeRefCounted!File` handles are created and deleted. // This allows us to see (after the scope) what happens after all handles have been destroyed. { // We move the content of `file` to a separate (and heap-allocated) `File` object, - // managed-and-accessed via one-or-multiple (initially: one) `RefCounted!File` objects ("handles"). + // managed-and-accessed via one-or-multiple (initially: one) `SafeRefCounted!File` objects ("handles"). // This "moving": // (1) invokes `file`'s destructor (=> `File.nDestroyed` is incremented from 0 to 1 and `file.name` becomes `null`); // (2) overwrites `file` with `File.init` (=> `file.name` becomes `null`). @@ -7057,18 +7385,18 @@ RefCounted!(T, RefCountedAutoInitialize.no) refCounted(T)(T val) // but please note that (2) is only performed if `File` defines a destructor (or post-blit operator), // and in the absence of the `nDestroyed` instrumentation there would have been no reason to define a destructor. import std.algorithm.mutation : move; - auto rcFile = refCounted(move(file)); + auto rcFile = safeRefCounted(move(file)); assert(rcFile.name == "name"); assert(File.nDestroyed == 1); assert(file.name == null); - // We create another `RefCounted!File` handle to the same separate `File` object. + // We create another `SafeRefCounted!File` handle to the same separate `File` object. // While any of the handles is still alive, the `File` object is kept alive (=> `File.nDestroyed` is not modified). auto rcFile2 = rcFile; assert(rcFile.refCountedStore.refCount == 2); assert(File.nDestroyed == 1); } - // The separate `File` object is deleted when the last `RefCounted!File` handle is destroyed + // The separate `File` object is deleted when the last `SafeRefCounted!File` handle is destroyed // (i.e. at the closing brace of the compound statement above, which destroys both handles: `rcFile` and `rcFile2`) // (=> `File.nDestroyed` is incremented again, from 1 to 2): assert(File.nDestroyed == 2); @@ -9783,3 +10111,269 @@ unittest Nullable!S s2 = s1; assert(s2.get().b == 3); } + +/// The old version of $(LREF SafeRefCounted), before $(LREF borrow) existed. +/// Old code may be relying on `@safe`ty of some of the member functions which +/// cannot be safe in the new scheme, and +/// can avoid breakage by continuing to use this. `SafeRefCounted` should be +/// preferred, as this type is outdated and unrecommended for new code. +struct RefCounted(T, RefCountedAutoInitialize autoInit = + RefCountedAutoInitialize.yes) +{ + version (D_BetterC) + { + private enum enableGCScan = false; + } + else + { + private enum enableGCScan = hasIndirections!T; + } + + // TODO remove pure when https://issues.dlang.org/show_bug.cgi?id=15862 has been fixed + extern(C) private pure nothrow @nogc static + { + pragma(mangle, "free") void pureFree( void *ptr ); + static if (enableGCScan) + { + pragma(mangle, "gc_addRange") void pureGcAddRange( in void* p, size_t sz, const TypeInfo ti = null ); + pragma(mangle, "gc_removeRange") void pureGcRemoveRange( in void* p ); + } + } + + struct RefCountedStore + { + private struct Impl + { + T _payload; + size_t _count; + } + + private Impl* _store; + + private void initialize(A...)(auto ref A args) + { + import core.lifetime : emplace, forward; + + allocateStore(); + version (D_Exceptions) scope(failure) deallocateStore(); + emplace(&_store._payload, forward!args); + _store._count = 1; + } + + private void move(ref T source) nothrow pure + { + import std.algorithm.mutation : moveEmplace; + + allocateStore(); + moveEmplace(source, _store._payload); + _store._count = 1; + } + + // 'nothrow': can only generate an Error + private void allocateStore() nothrow pure + { + static if (enableGCScan) + { + import std.internal.memory : enforceCalloc; + _store = cast(Impl*) enforceCalloc(1, Impl.sizeof); + pureGcAddRange(&_store._payload, T.sizeof); + } + else + { + import std.internal.memory : enforceMalloc; + _store = cast(Impl*) enforceMalloc(Impl.sizeof); + } + } + + private void deallocateStore() nothrow pure + { + static if (enableGCScan) + { + pureGcRemoveRange(&this._store._payload); + } + pureFree(_store); + _store = null; + } + + @property nothrow @safe pure @nogc + bool isInitialized() const + { + return _store !is null; + } + + @property nothrow @safe pure @nogc + size_t refCount() const + { + return isInitialized ? _store._count : 0; + } + + void ensureInitialized()() + { + // By checking for `@disable this()` and failing early we can + // produce a clearer error message. + static assert(__traits(compiles, { static T t; }), + "Cannot automatically initialize `" ~ fullyQualifiedName!T ~ + "` because `" ~ fullyQualifiedName!T ~ + ".this()` is annotated with `@disable`."); + if (!isInitialized) initialize(); + } + + } + RefCountedStore _refCounted; + + @property nothrow @safe + ref inout(RefCountedStore) refCountedStore() inout + { + return _refCounted; + } + + this(A...)(auto ref A args) if (A.length > 0) + out + { + assert(refCountedStore.isInitialized); + } + do + { + import core.lifetime : forward; + _refCounted.initialize(forward!args); + } + + this(T val) + { + _refCounted.move(val); + } + + this(this) @safe pure nothrow @nogc + { + if (!_refCounted.isInitialized) return; + ++_refCounted._store._count; + } + + ~this() + { + if (!_refCounted.isInitialized) return; + assert(_refCounted._store._count > 0); + if (--_refCounted._store._count) + return; + // Done, destroy and deallocate + .destroy(_refCounted._store._payload); + _refCounted.deallocateStore(); + } + + void opAssign(typeof(this) rhs) + { + import std.algorithm.mutation : swap; + + swap(_refCounted._store, rhs._refCounted._store); + } + + void opAssign(T rhs) + { + import std.algorithm.mutation : move; + + static if (autoInit == RefCountedAutoInitialize.yes) + { + _refCounted.ensureInitialized(); + } + else + { + assert(_refCounted.isInitialized); + } + move(rhs, _refCounted._store._payload); + } + + static if (autoInit == RefCountedAutoInitialize.yes) + { + //Can't use inout here because of potential mutation + @property + ref T refCountedPayload() return + { + _refCounted.ensureInitialized(); + return _refCounted._store._payload; + } + } + + @property nothrow @safe pure @nogc + ref inout(T) refCountedPayload() inout return + { + assert(_refCounted.isInitialized, "Attempted to access an uninitialized payload."); + return _refCounted._store._payload; + } + + alias refCountedPayload this; + + static if (is(T == struct) && !is(typeof((ref T t) => t.toString()))) + { + string toString(this This)() + { + import std.conv : to; + + static if (autoInit) + return to!string(refCountedPayload); + else + { + if (!_refCounted.isInitialized) + return This.stringof ~ "(RefCountedStore(null))"; + else + return to!string(_refCounted._store._payload); + } + } + } +} + +/// +@betterC pure @system nothrow @nogc unittest +{ + auto rc1 = RefCounted!int(5); + assert(rc1 == 5); + auto rc2 = rc1; + rc2 = 42; + assert(rc1 == 42); +} + +// More unit tests below SafeRefCounted + +/** + * Like $(LREF safeRefCounted) but used to initialize $(LREF RefCounted) + * instead. Intended for backwards compatibility, otherwise it is preferable + * to use `safeRefCounted`. + */ +RefCounted!(T, RefCountedAutoInitialize.no) refCounted(T)(T val) +{ + typeof(return) res; + res._refCounted.move(val); + return res; +} + +/// +@system unittest +{ + static struct File + { + static size_t nDestroyed; + string name; + @disable this(this); // not copyable + ~this() { name = null; ++nDestroyed; } + } + + auto file = File("name"); + assert(file.name == "name"); + static assert(!__traits(compiles, {auto file2 = file;})); + assert(File.nDestroyed == 0); + + { + import std.algorithm.mutation : move; + auto rcFile = refCounted(move(file)); + assert(rcFile.name == "name"); + assert(File.nDestroyed == 1); + assert(file.name == null); + + auto rcFile2 = rcFile; + assert(rcFile.refCountedStore.refCount == 2); + assert(File.nDestroyed == 1); + } + + assert(File.nDestroyed == 2); +} + +// More unit tests below safeRefCounted diff --git a/std/uni/package.d b/std/uni/package.d index e12a70cfe80..8a032aaa514 100644 --- a/std/uni/package.d +++ b/std/uni/package.d @@ -426,7 +426,7 @@ $(TR $(TD Building blocks) $(TD $(SECTION Construction of lookup tables) $(P The Unicode standard describes a set of algorithms that depend on having the ability to quickly look up various properties - of a code point. Given the the codespace of about 1 million $(CODEPOINTS), + of a code point. Given the codespace of about 1 million $(CODEPOINTS), it is not a trivial task to provide a space-efficient solution for the multitude of properties. ) diff --git a/std/utf.d b/std/utf.d index d22dac8b15d..8d94e121ff2 100644 --- a/std/utf.d +++ b/std/utf.d @@ -3571,7 +3571,7 @@ enum dchar replacementDchar = '\uFFFD'; * of characters (including strings) or a type that implicitly converts to a string type. * Returns: * If `r` is not an auto-decodable string (i.e. a narrow string or a - * user-defined type that implicits converts to a string type), then `r` + * user-defined type that implicitly converts to a string type), then `r` * is returned. * * Otherwise, `r` is converted to its corresponding string type (if it's diff --git a/std/xml.d b/std/xml.d deleted file mode 100644 index fdfdc3f70ab..00000000000 --- a/std/xml.d +++ /dev/null @@ -1,3113 +0,0 @@ -// Written in the D programming language. - -/** -$(RED Warning: This module is considered out-dated and not up to Phobos' - current standards. It will be removed from Phobos in 2.101.0. - If you still need it, go to $(LINK https://github.com/DigitalMars/undeaD)) - */ - -/* -Classes and functions for creating and parsing XML - -The basic architecture of this module is that there are standalone functions, -classes for constructing an XML document from scratch (Tag, Element and -Document), and also classes for parsing a pre-existing XML file (ElementParser -and DocumentParser). The parsing classes may be used to build a -Document, but that is not their primary purpose. The handling capabilities of -DocumentParser and ElementParser are sufficiently customizable that you can -make them do pretty much whatever you want. - -Example: This example creates a DOM (Document Object Model) tree - from an XML file. ------------------------------------------------------------------------------- -import std.xml; -import std.stdio; -import std.string; -import std.file; - -// books.xml is used in various samples throughout the Microsoft XML Core -// Services (MSXML) SDK. -// -// See http://msdn2.microsoft.com/en-us/library/ms762271(VS.85).aspx - -void main() -{ - string s = cast(string) std.file.read("books.xml"); - - // Check for well-formedness - check(s); - - // Make a DOM tree - auto doc = new Document(s); - - // Plain-print it - writeln(doc); -} ------------------------------------------------------------------------------- - -Example: This example does much the same thing, except that the file is - deconstructed and reconstructed by hand. This is more work, but the - techniques involved offer vastly more power. ------------------------------------------------------------------------------- -import std.xml; -import std.stdio; -import std.string; - -struct Book -{ - string id; - string author; - string title; - string genre; - string price; - string pubDate; - string description; -} - -void main() -{ - string s = cast(string) std.file.read("books.xml"); - - // Check for well-formedness - check(s); - - // Take it apart - Book[] books; - - auto xml = new DocumentParser(s); - xml.onStartTag["book"] = (ElementParser xml) - { - Book book; - book.id = xml.tag.attr["id"]; - - xml.onEndTag["author"] = (in Element e) { book.author = e.text(); }; - xml.onEndTag["title"] = (in Element e) { book.title = e.text(); }; - xml.onEndTag["genre"] = (in Element e) { book.genre = e.text(); }; - xml.onEndTag["price"] = (in Element e) { book.price = e.text(); }; - xml.onEndTag["publish-date"] = (in Element e) { book.pubDate = e.text(); }; - xml.onEndTag["description"] = (in Element e) { book.description = e.text(); }; - - xml.parse(); - - books ~= book; - }; - xml.parse(); - - // Put it back together again; - auto doc = new Document(new Tag("catalog")); - foreach (book;books) - { - auto element = new Element("book"); - element.tag.attr["id"] = book.id; - - element ~= new Element("author", book.author); - element ~= new Element("title", book.title); - element ~= new Element("genre", book.genre); - element ~= new Element("price", book.price); - element ~= new Element("publish-date",book.pubDate); - element ~= new Element("description", book.description); - - doc ~= element; - } - - // Pretty-print it - writefln(join(doc.pretty(3),"\n")); -} -------------------------------------------------------------------------------- -Copyright: Copyright Janice Caron 2008 - 2009. -License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). -Authors: Janice Caron -Source: $(PHOBOSSRC std/xml.d) -*/ -/* - Copyright Janice Caron 2008 - 2009. -Distributed under the Boost Software License, Version 1.0. - (See accompanying file LICENSE_1_0.txt or copy at - http://www.boost.org/LICENSE_1_0.txt) -*/ -deprecated("Will be removed from Phobos in 2.101.0. If you still need it, go to https://github.com/DigitalMars/undeaD") -module std.xml; - -enum cdata = "= 0x20) - return true; - switch (c) - { - case 0xA: - case 0x9: - case 0xD: - return true; - default: - return false; - } - } - else if (0xE000 <= c && c <= 0x10FFFF) - { - if ((c & 0x1FFFFE) != 0xFFFE) // U+FFFE and U+FFFF - return true; - } - return false; -} - -@safe @nogc nothrow pure unittest -{ - assert(!isChar(cast(dchar) 0x8)); - assert( isChar(cast(dchar) 0x9)); - assert( isChar(cast(dchar) 0xA)); - assert(!isChar(cast(dchar) 0xB)); - assert(!isChar(cast(dchar) 0xC)); - assert( isChar(cast(dchar) 0xD)); - assert(!isChar(cast(dchar) 0xE)); - assert(!isChar(cast(dchar) 0x1F)); - assert( isChar(cast(dchar) 0x20)); - assert( isChar('J')); - assert( isChar(cast(dchar) 0xD7FF)); - assert(!isChar(cast(dchar) 0xD800)); - assert(!isChar(cast(dchar) 0xDFFF)); - assert( isChar(cast(dchar) 0xE000)); - assert( isChar(cast(dchar) 0xFFFD)); - assert(!isChar(cast(dchar) 0xFFFE)); - assert(!isChar(cast(dchar) 0xFFFF)); - assert( isChar(cast(dchar) 0x10000)); - assert( isChar(cast(dchar) 0x10FFFF)); - assert(!isChar(cast(dchar) 0x110000)); - - debug (stdxml_TestHardcodedChecks) - { - foreach (c; 0 .. dchar.max + 1) - assert(isChar(c) == lookup(CharTable, c)); - } -} - -/* - * Returns true if the character is whitespace according to the XML standard - * - * Only the following characters are considered whitespace in XML - space, tab, - * carriage return and linefeed - * - * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) - * - * Params: - * c = the character to be tested - */ -bool isSpace(dchar c) @safe @nogc pure nothrow -{ - return c == '\u0020' || c == '\u0009' || c == '\u000A' || c == '\u000D'; -} - -/* - * Returns true if the character is a digit according to the XML standard - * - * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) - * - * Params: - * c = the character to be tested - */ -bool isDigit(dchar c) @safe @nogc pure nothrow -{ - if (c <= 0x0039 && c >= 0x0030) - return true; - else - return lookup(DigitTable,c); -} - -@safe @nogc nothrow pure unittest -{ - debug (stdxml_TestHardcodedChecks) - { - foreach (c; 0 .. dchar.max + 1) - assert(isDigit(c) == lookup(DigitTable, c)); - } -} - -/* - * Returns true if the character is a letter according to the XML standard - * - * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) - * - * Params: - * c = the character to be tested - */ -bool isLetter(dchar c) @safe @nogc nothrow pure // rule 84 -{ - return isIdeographic(c) || isBaseChar(c); -} - -/* - * Returns true if the character is an ideographic character according to the - * XML standard - * - * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) - * - * Params: - * c = the character to be tested - */ -bool isIdeographic(dchar c) @safe @nogc nothrow pure -{ - if (c == 0x3007) - return true; - if (c <= 0x3029 && c >= 0x3021 ) - return true; - if (c <= 0x9FA5 && c >= 0x4E00) - return true; - return false; -} - -@safe @nogc nothrow pure unittest -{ - assert(isIdeographic('\u4E00')); - assert(isIdeographic('\u9FA5')); - assert(isIdeographic('\u3007')); - assert(isIdeographic('\u3021')); - assert(isIdeographic('\u3029')); - - debug (stdxml_TestHardcodedChecks) - { - foreach (c; 0 .. dchar.max + 1) - assert(isIdeographic(c) == lookup(IdeographicTable, c)); - } -} - -/* - * Returns true if the character is a base character according to the XML - * standard - * - * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) - * - * Params: - * c = the character to be tested - */ -bool isBaseChar(dchar c) @safe @nogc nothrow pure -{ - return lookup(BaseCharTable,c); -} - -/* - * Returns true if the character is a combining character according to the - * XML standard - * - * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) - * - * Params: - * c = the character to be tested - */ -bool isCombiningChar(dchar c) @safe @nogc nothrow pure -{ - return lookup(CombiningCharTable,c); -} - -/* - * Returns true if the character is an extender according to the XML standard - * - * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) - * - * Params: - * c = the character to be tested - */ -bool isExtender(dchar c) @safe @nogc nothrow pure -{ - return lookup(ExtenderTable,c); -} - -/* - * Encodes a string by replacing all characters which need to be escaped with - * appropriate predefined XML entities. - * - * encode() escapes certain characters (ampersand, quote, apostrophe, less-than - * and greater-than), and similarly, decode() unescapes them. These functions - * are provided for convenience only. You do not need to use them when using - * the std.xml classes, because then all the encoding and decoding will be done - * for you automatically. - * - * If the string is not modified, the original will be returned. - * - * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) - * - * Params: - * s = The string to be encoded - * - * Returns: The encoded string - * - * Example: - * -------------- - * writefln(encode("a > b")); // writes "a > b" - * -------------- - */ -S encode(S)(S s) -{ - import std.array : appender; - - string r; - size_t lastI; - auto result = appender!S(); - - foreach (i, c; s) - { - switch (c) - { - case '&': r = "&"; break; - case '"': r = """; break; - case '\'': r = "'"; break; - case '<': r = "<"; break; - case '>': r = ">"; break; - default: continue; - } - // Replace with r - result.put(s[lastI .. i]); - result.put(r); - lastI = i + 1; - } - - if (!result.data.ptr) return s; - result.put(s[lastI .. $]); - return result.data; -} - -@safe pure unittest -{ - auto s = "hello"; - assert(encode(s) is s); - assert(encode("a > b") == "a > b", encode("a > b")); - assert(encode("a < b") == "a < b"); - assert(encode("don't") == "don't"); - assert(encode("\"hi\"") == ""hi"", encode("\"hi\"")); - assert(encode("cat & dog") == "cat & dog"); -} - -/* - * Mode to use for decoding. - * - * $(DDOC_ENUM_MEMBERS NONE) Do not decode - * $(DDOC_ENUM_MEMBERS LOOSE) Decode, but ignore errors - * $(DDOC_ENUM_MEMBERS STRICT) Decode, and throw exception on error - */ -enum DecodeMode -{ - NONE, LOOSE, STRICT -} - -/* - * Decodes a string by unescaping all predefined XML entities. - * - * encode() escapes certain characters (ampersand, quote, apostrophe, less-than - * and greater-than), and similarly, decode() unescapes them. These functions - * are provided for convenience only. You do not need to use them when using - * the std.xml classes, because then all the encoding and decoding will be done - * for you automatically. - * - * This function decodes the entities &amp;, &quot;, &apos;, - * &lt; and &gt, - * as well as decimal and hexadecimal entities such as &#x20AC; - * - * If the string does not contain an ampersand, the original will be returned. - * - * Note that the "mode" parameter can be one of DecodeMode.NONE (do not - * decode), DecodeMode.LOOSE (decode, but ignore errors), or DecodeMode.STRICT - * (decode, and throw a DecodeException in the event of an error). - * - * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) - * - * Params: - * s = The string to be decoded - * mode = (optional) Mode to use for decoding. (Defaults to LOOSE). - * - * Throws: DecodeException if mode == DecodeMode.STRICT and decode fails - * - * Returns: The decoded string - * - * Example: - * -------------- - * writefln(decode("a > b")); // writes "a > b" - * -------------- - */ -string decode(string s, DecodeMode mode=DecodeMode.LOOSE) @safe pure -{ - import std.algorithm.searching : startsWith; - - if (mode == DecodeMode.NONE) return s; - - string buffer; - foreach (ref i; 0 .. s.length) - { - char c = s[i]; - if (c != '&') - { - if (buffer.length != 0) buffer ~= c; - } - else - { - if (buffer.length == 0) - { - buffer = s[0 .. i].dup; - } - if (startsWith(s[i..$],"&#")) - { - try - { - dchar d; - string t = s[i..$]; - checkCharRef(t, d); - char[4] temp; - import std.utf : encode; - buffer ~= temp[0 .. encode(temp, d)]; - i = s.length - t.length - 1; - } - catch (Err e) - { - if (mode == DecodeMode.STRICT) - throw new DecodeException("Unescaped &"); - buffer ~= '&'; - } - } - else if (startsWith(s[i..$],"&" )) { buffer ~= '&'; i += 4; } - else if (startsWith(s[i..$],""")) { buffer ~= '"'; i += 5; } - else if (startsWith(s[i..$],"'")) { buffer ~= '\''; i += 5; } - else if (startsWith(s[i..$],"<" )) { buffer ~= '<'; i += 3; } - else if (startsWith(s[i..$],">" )) { buffer ~= '>'; i += 3; } - else - { - if (mode == DecodeMode.STRICT) - throw new DecodeException("Unescaped &"); - buffer ~= '&'; - } - } - } - return (buffer.length == 0) ? s : buffer; -} - -@safe pure unittest -{ - void assertNot(string s) pure - { - bool b = false; - try { decode(s,DecodeMode.STRICT); } - catch (DecodeException e) { b = true; } - assert(b,s); - } - - // Assert that things that should work, do - auto s = "hello"; - assert(decode(s, DecodeMode.STRICT) is s); - assert(decode("a > b", DecodeMode.STRICT) == "a > b"); - assert(decode("a < b", DecodeMode.STRICT) == "a < b"); - assert(decode("don't", DecodeMode.STRICT) == "don't"); - assert(decode(""hi"", DecodeMode.STRICT) == "\"hi\""); - assert(decode("cat & dog", DecodeMode.STRICT) == "cat & dog"); - assert(decode("*", DecodeMode.STRICT) == "*"); - assert(decode("*", DecodeMode.STRICT) == "*"); - assert(decode("cat & dog", DecodeMode.LOOSE) == "cat & dog"); - assert(decode("a > b", DecodeMode.LOOSE) == "a > b"); - assert(decode("&#;", DecodeMode.LOOSE) == "&#;"); - assert(decode("&#x;", DecodeMode.LOOSE) == "&#x;"); - assert(decode("G;", DecodeMode.LOOSE) == "G;"); - assert(decode("G;", DecodeMode.LOOSE) == "G;"); - - // Assert that things that shouldn't work, don't - assertNot("cat & dog"); - assertNot("a > b"); - assertNot("&#;"); - assertNot("&#x;"); - assertNot("G;"); - assertNot("G;"); -} - -/* - * Class representing an XML document. - * - * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) - * - */ -class Document : Element -{ - /* - * Contains all text which occurs before the root element. - * Defaults to <?xml version="1.0"?> - */ - string prolog = ""; - /* - * Contains all text which occurs after the root element. - * Defaults to the empty string - */ - string epilog; - - /* - * Constructs a Document by parsing XML text. - * - * This function creates a complete DOM (Document Object Model) tree. - * - * The input to this function MUST be valid XML. - * This is enforced by DocumentParser's in contract. - * - * Params: - * s = the complete XML text. - */ - this(string s) - in - { - assert(s.length != 0); - } - do - { - auto xml = new DocumentParser(s); - string tagString = xml.tag.tagString; - - this(xml.tag); - prolog = s[0 .. tagString.ptr - s.ptr]; - parse(xml); - epilog = *xml.s; - } - - /* - * Constructs a Document from a Tag. - * - * Params: - * tag = the start tag of the document. - */ - this(const(Tag) tag) - { - super(tag); - } - - const - { - /* - * Compares two Documents for equality - * - * Example: - * -------------- - * Document d1,d2; - * if (d1 == d2) { } - * -------------- - */ - override bool opEquals(scope const Object o) const - { - const doc = toType!(const Document)(o); - return prolog == doc.prolog - && (cast(const) this).Element.opEquals(cast(const) doc) - && epilog == doc.epilog; - } - - /* - * Compares two Documents - * - * You should rarely need to call this function. It exists so that - * Documents can be used as associative array keys. - * - * Example: - * -------------- - * Document d1,d2; - * if (d1 < d2) { } - * -------------- - */ - override int opCmp(scope const Object o) scope const - { - const doc = toType!(const Document)(o); - if (prolog != doc.prolog) - return prolog < doc.prolog ? -1 : 1; - if (int cmp = this.Element.opCmp(doc)) - return cmp; - if (epilog != doc.epilog) - return epilog < doc.epilog ? -1 : 1; - return 0; - } - - /* - * Returns the hash of a Document - * - * You should rarely need to call this function. It exists so that - * Documents can be used as associative array keys. - */ - override size_t toHash() scope const @trusted - { - return hash(prolog, hash(epilog, (cast() this).Element.toHash())); - } - - /* - * Returns the string representation of a Document. (That is, the - * complete XML of a document). - */ - override string toString() scope const @safe - { - return prolog ~ super.toString() ~ epilog; - } - } -} - -@system unittest -{ - // https://issues.dlang.org/show_bug.cgi?id=14966 - auto xml = ``; - - auto a = new Document(xml); - auto b = new Document(xml); - assert(a == b); - assert(!(a < b)); - int[Document] aa; - aa[a] = 1; - assert(aa[b] == 1); - - b ~= new Element("b"); - assert(a < b); - assert(b > a); -} - -/* - * Class representing an XML element. - * - * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) - */ -class Element : Item -{ - Tag tag; // The start tag of the element - Item[] items; // The element's items - Text[] texts; // The element's text items - CData[] cdatas; // The element's CData items - Comment[] comments; // The element's comments - ProcessingInstruction[] pis; // The element's processing instructions - Element[] elements; // The element's child elements - - /* - * Constructs an Element given a name and a string to be used as a Text - * interior. - * - * Params: - * name = the name of the element. - * interior = (optional) the string interior. - * - * Example: - * ------------------------------------------------------- - * auto element = new Element("title","Serenity") - * // constructs the element Serenity - * ------------------------------------------------------- - */ - this(string name, string interior=null) @safe pure - { - this(new Tag(name)); - if (interior.length != 0) opOpAssign!("~")(new Text(interior)); - } - - /* - * Constructs an Element from a Tag. - * - * Params: - * tag_ = the start or empty tag of the element. - */ - this(const(Tag) tag_) @safe pure - { - this.tag = new Tag(tag_.name); - tag.type = TagType.EMPTY; - foreach (k,v;tag_.attr) tag.attr[k] = v; - tag.tagString = tag_.tagString; - } - - /* - * Append a text item to the interior of this element - * - * Params: - * item = the item you wish to append. - * - * Example: - * -------------- - * Element element; - * element ~= new Text("hello"); - * -------------- - */ - void opOpAssign(string op)(Text item) @safe pure - if (op == "~") - { - texts ~= item; - appendItem(item); - } - - /* - * Append a CData item to the interior of this element - * - * Params: - * item = the item you wish to append. - * - * Example: - * -------------- - * Element element; - * element ~= new CData("hello"); - * -------------- - */ - void opOpAssign(string op)(CData item) @safe pure - if (op == "~") - { - cdatas ~= item; - appendItem(item); - } - - /* - * Append a comment to the interior of this element - * - * Params: - * item = the item you wish to append. - * - * Example: - * -------------- - * Element element; - * element ~= new Comment("hello"); - * -------------- - */ - void opOpAssign(string op)(Comment item) @safe pure - if (op == "~") - { - comments ~= item; - appendItem(item); - } - - /* - * Append a processing instruction to the interior of this element - * - * Params: - * item = the item you wish to append. - * - * Example: - * -------------- - * Element element; - * element ~= new ProcessingInstruction("hello"); - * -------------- - */ - void opOpAssign(string op)(ProcessingInstruction item) @safe pure - if (op == "~") - { - pis ~= item; - appendItem(item); - } - - /* - * Append a complete element to the interior of this element - * - * Params: - * item = the item you wish to append. - * - * Example: - * -------------- - * Element element; - * Element other = new Element("br"); - * element ~= other; - * // appends element representing
- * -------------- - */ - void opOpAssign(string op)(Element item) @safe pure - if (op == "~") - { - elements ~= item; - appendItem(item); - } - - private void appendItem(Item item) @safe pure - { - items ~= item; - if (tag.type == TagType.EMPTY && !item.isEmptyXML) - tag.type = TagType.START; - } - - private void parse(ElementParser xml) - { - xml.onText = (string s) { opOpAssign!("~")(new Text(s)); }; - xml.onCData = (string s) { opOpAssign!("~")(new CData(s)); }; - xml.onComment = (string s) { opOpAssign!("~")(new Comment(s)); }; - xml.onPI = (string s) { opOpAssign!("~")(new ProcessingInstruction(s)); }; - - xml.onStartTag[null] = (ElementParser xml) - { - auto e = new Element(xml.tag); - e.parse(xml); - opOpAssign!("~")(e); - }; - - xml.parse(); - } - - /* - * Compares two Elements for equality - * - * Example: - * -------------- - * Element e1,e2; - * if (e1 == e2) { } - * -------------- - */ - override bool opEquals(scope const Object o) const - { - const element = toType!(const Element)(o); - immutable len = items.length; - if (len != element.items.length) return false; - foreach (i; 0 .. len) - { - if (!items[i].opEquals(element.items[i])) return false; - } - return true; - } - - /* - * Compares two Elements - * - * You should rarely need to call this function. It exists so that Elements - * can be used as associative array keys. - * - * Example: - * -------------- - * Element e1,e2; - * if (e1 < e2) { } - * -------------- - */ - override int opCmp(scope const Object o) @safe const - { - const element = toType!(const Element)(o); - for (uint i=0; ; ++i) - { - if (i == items.length && i == element.items.length) return 0; - if (i == items.length) return -1; - if (i == element.items.length) return 1; - if (!items[i].opEquals(element.items[i])) - return items[i].opCmp(element.items[i]); - } - } - - /* - * Returns the hash of an Element - * - * You should rarely need to call this function. It exists so that Elements - * can be used as associative array keys. - */ - override size_t toHash() scope const @safe - { - size_t hash = tag.toHash(); - foreach (item;items) hash += item.toHash(); - return hash; - } - - const - { - /* - * Returns the decoded interior of an element. - * - * The element is assumed to contain text only. So, for - * example, given XML such as "<title>Good &amp; - * Bad</title>", will return "Good & Bad". - * - * Params: - * mode = (optional) Mode to use for decoding. (Defaults to LOOSE). - * - * Throws: DecodeException if decode fails - */ - string text(DecodeMode mode=DecodeMode.LOOSE) - { - string buffer; - foreach (item;items) - { - Text t = cast(Text) item; - if (t is null) throw new DecodeException(item.toString()); - buffer ~= decode(t.toString(),mode); - } - return buffer; - } - - /* - * Returns an indented string representation of this item - * - * Params: - * indent = (optional) number of spaces by which to indent this - * element. Defaults to 2. - */ - override string[] pretty(uint indent=2) scope - { - import std.algorithm.searching : count; - import std.string : rightJustify; - - if (isEmptyXML) return [ tag.toEmptyString() ]; - - if (items.length == 1) - { - auto t = cast(const(Text))(items[0]); - if (t !is null) - { - return [tag.toStartString() ~ t.toString() ~ tag.toEndString()]; - } - } - - string[] a = [ tag.toStartString() ]; - foreach (item;items) - { - string[] b = item.pretty(indent); - foreach (s;b) - { - a ~= rightJustify(s,count(s) + indent); - } - } - a ~= tag.toEndString(); - return a; - } - - /* - * Returns the string representation of an Element - * - * Example: - * -------------- - * auto element = new Element("br"); - * writefln(element.toString()); // writes "
" - * -------------- - */ - override string toString() scope @safe - { - if (isEmptyXML) return tag.toEmptyString(); - - string buffer = tag.toStartString(); - foreach (item;items) { buffer ~= item.toString(); } - buffer ~= tag.toEndString(); - return buffer; - } - - override @property @safe pure @nogc nothrow bool isEmptyXML() const scope { return items.length == 0; } - } -} - -/* - * Tag types. - * - * $(DDOC_ENUM_MEMBERS START) Used for start tags - * $(DDOC_ENUM_MEMBERS END) Used for end tags - * $(DDOC_ENUM_MEMBERS EMPTY) Used for empty tags - * - */ -enum TagType { START, END, EMPTY } - -/* - * Class representing an XML tag. - * - * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) - * - * The class invariant guarantees - * - */ -class Tag -{ - TagType type = TagType.START; // Type of tag - string name; // Tag name - string[string] attr; // Associative array of attributes - private string tagString; - - invariant() - { - string s; - string t; - - assert(type == TagType.START - || type == TagType.END - || type == TagType.EMPTY); - - s = name; - try { checkName(s,t); } - catch (Err e) { assert(false,"Invalid tag name:" ~ e.toString()); } - - foreach (k,v;attr) - { - s = k; - try { checkName(s,t); } - catch (Err e) - { assert(false,"Invalid attribute name:" ~ e.toString()); } - } - } - - /* - * Constructs an instance of Tag with a specified name and type - * - * The constructor does not initialize the attributes. To initialize the - * attributes, you access the $(B attr) member variable. - * - * Params: - * name = the Tag's name - * type = (optional) the Tag's type. If omitted, defaults to - * TagType.START. - * - * Example: - * -------------- - * auto tag = new Tag("img",Tag.EMPTY); - * tag.attr["src"] = "http://example.com/example.jpg"; - * -------------- - */ - this(string name, TagType type=TagType.START) @safe pure - { - this.name = name; - this.type = type; - } - - /* Private constructor (so don't ddoc this!) - * - * Constructs a Tag by parsing the string representation, e.g. "". - * - * The string is passed by reference, and is advanced over all characters - * consumed. - * - * The second parameter is a dummy parameter only, required solely to - * distinguish this constructor from the public one. - */ - private this(ref string s, bool dummy) @safe pure - { - import std.algorithm.searching : countUntil; - import std.ascii : isWhite; - import std.utf : byCodeUnit; - - tagString = s; - try - { - reqc(s,'<'); - if (optc(s,'/')) type = TagType.END; - ptrdiff_t i = s.byCodeUnit.countUntil(">", "/>", " ", "\t", "\v", "\r", "\n", "\f"); - name = s[0 .. i]; - s = s[i .. $]; - - i = s.byCodeUnit.countUntil!(a => !isWhite(a)); - s = s[i .. $]; - - while (s.length > 0 && s[0] != '>' && s[0] != '/') - { - i = s.byCodeUnit.countUntil("=", " ", "\t", "\v", "\r", "\n", "\f"); - string key = s[0 .. i]; - s = s[i .. $]; - - i = s.byCodeUnit.countUntil!(a => !isWhite(a)); - s = s[i .. $]; - reqc(s,'='); - i = s.byCodeUnit.countUntil!(a => !isWhite(a)); - s = s[i .. $]; - - immutable char quote = requireOneOf(s,"'\""); - i = s.byCodeUnit.countUntil(quote); - string val = decode(s[0 .. i], DecodeMode.LOOSE); - s = s[i .. $]; - reqc(s,quote); - - i = s.byCodeUnit.countUntil!(a => !isWhite(a)); - s = s[i .. $]; - attr[key] = val; - } - if (optc(s,'/')) - { - if (type == TagType.END) throw new TagException(""); - type = TagType.EMPTY; - } - reqc(s,'>'); - tagString.length = tagString.length - s.length; - } - catch (XMLException e) - { - tagString.length = tagString.length - s.length; - throw new TagException(tagString); - } - } - - const - { - /* - * Compares two Tags for equality - * - * You should rarely need to call this function. It exists so that Tags - * can be used as associative array keys. - * - * Example: - * -------------- - * Tag tag1,tag2 - * if (tag1 == tag2) { } - * -------------- - */ - override bool opEquals(scope Object o) - { - const tag = toType!(const Tag)(o); - return - (name != tag.name) ? false : ( - (attr != tag.attr) ? false : ( - (type != tag.type) ? false : ( - true ))); - } - - /* - * Compares two Tags - * - * Example: - * -------------- - * Tag tag1,tag2 - * if (tag1 < tag2) { } - * -------------- - */ - override int opCmp(Object o) - { - const tag = toType!(const Tag)(o); - // Note that attr is an AA, so the comparison is nonsensical (bug 10381) - return - ((name != tag.name) ? ( name < tag.name ? -1 : 1 ) : - ((attr != tag.attr) ? ( cast(void *) attr < cast(void*) tag.attr ? -1 : 1 ) : - ((type != tag.type) ? ( type < tag.type ? -1 : 1 ) : - 0 ))); - } - - /* - * Returns the hash of a Tag - * - * You should rarely need to call this function. It exists so that Tags - * can be used as associative array keys. - */ - override size_t toHash() - { - return .hashOf(name); - } - - /* - * Returns the string representation of a Tag - * - * Example: - * -------------- - * auto tag = new Tag("book",TagType.START); - * writefln(tag.toString()); // writes "" - * -------------- - */ - override string toString() @safe - { - if (isEmpty) return toEmptyString(); - return (isEnd) ? toEndString() : toStartString(); - } - - private - { - string toNonEndString() @safe - { - import std.format : format; - - string s = "<" ~ name; - foreach (key,val;attr) - s ~= format(" %s=\"%s\"",key,encode(val)); - return s; - } - - string toStartString() @safe { return toNonEndString() ~ ">"; } - - string toEndString() @safe { return ""; } - - string toEmptyString() @safe { return toNonEndString() ~ " />"; } - } - - /* - * Returns true if the Tag is a start tag - * - * Example: - * -------------- - * if (tag.isStart) { } - * -------------- - */ - @property bool isStart() @safe @nogc pure nothrow { return type == TagType.START; } - - /* - * Returns true if the Tag is an end tag - * - * Example: - * -------------- - * if (tag.isEnd) { } - * -------------- - */ - @property bool isEnd() @safe @nogc pure nothrow { return type == TagType.END; } - - /* - * Returns true if the Tag is an empty tag - * - * Example: - * -------------- - * if (tag.isEmpty) { } - * -------------- - */ - @property bool isEmpty() @safe @nogc pure nothrow { return type == TagType.EMPTY; } - } -} - -/* - * Class representing a comment - */ -class Comment : Item -{ - private string content; - - /* - * Construct a comment - * - * Params: - * content = the body of the comment - * - * Throws: CommentException if the comment body is illegal (contains "--" - * or exactly equals "-") - * - * Example: - * -------------- - * auto item = new Comment("This is a comment"); - * // constructs - * -------------- - */ - this(string content) @safe pure - { - import std.string : indexOf; - - if (content == "-" || content.indexOf("--") != -1) - throw new CommentException(content); - this.content = content; - } - - /* - * Compares two comments for equality - * - * Example: - * -------------- - * Comment item1,item2; - * if (item1 == item2) { } - * -------------- - */ - override bool opEquals(scope const Object o) const - { - const item = toType!(const Item)(o); - const t = cast(const Comment) item; - return t !is null && content == t.content; - } - - /* - * Compares two comments - * - * You should rarely need to call this function. It exists so that Comments - * can be used as associative array keys. - * - * Example: - * -------------- - * Comment item1,item2; - * if (item1 < item2) { } - * -------------- - */ - override int opCmp(scope const Object o) scope const - { - const item = toType!(const Item)(o); - const t = cast(const Comment) item; - return t !is null && (content != t.content - ? (content < t.content ? -1 : 1 ) : 0 ); - } - - /* - * Returns the hash of a Comment - * - * You should rarely need to call this function. It exists so that Comments - * can be used as associative array keys. - */ - override size_t toHash() scope const nothrow { return hash(content); } - - /* - * Returns a string representation of this comment - */ - override string toString() scope const @safe pure nothrow { return ""; } - - override @property @safe @nogc pure nothrow scope bool isEmptyXML() const { return false; } // Returns false always -} - -// https://issues.dlang.org/show_bug.cgi?id=16241 -@safe unittest -{ - import std.exception : assertThrown; - auto c = new Comment("=="); - assert(c.content == "=="); - assertThrown!CommentException(new Comment("--")); -} - -/* - * Class representing a Character Data section - */ -class CData : Item -{ - private string content; - - /* - * Construct a character data section - * - * Params: - * content = the body of the character data segment - * - * Throws: CDataException if the segment body is illegal (contains "]]>") - * - * Example: - * -------------- - * auto item = new CData("hello"); - * // constructs hello]]> - * -------------- - */ - this(string content) @safe pure - { - import std.string : indexOf; - if (content.indexOf("]]>") != -1) throw new CDataException(content); - this.content = content; - } - - /* - * Compares two CDatas for equality - * - * Example: - * -------------- - * CData item1,item2; - * if (item1 == item2) { } - * -------------- - */ - override bool opEquals(scope const Object o) const - { - const item = toType!(const Item)(o); - const t = cast(const CData) item; - return t !is null && content == t.content; - } - - /* - * Compares two CDatas - * - * You should rarely need to call this function. It exists so that CDatas - * can be used as associative array keys. - * - * Example: - * -------------- - * CData item1,item2; - * if (item1 < item2) { } - * -------------- - */ - override int opCmp(scope const Object o) scope const - { - const item = toType!(const Item)(o); - const t = cast(const CData) item; - return t !is null && (content != t.content - ? (content < t.content ? -1 : 1 ) : 0 ); - } - - /* - * Returns the hash of a CData - * - * You should rarely need to call this function. It exists so that CDatas - * can be used as associative array keys. - */ - override size_t toHash() scope const nothrow { return hash(content); } - - /* - * Returns a string representation of this CData section - */ - override string toString() scope const @safe pure nothrow { return cdata ~ content ~ "]]>"; } - - override @property @safe @nogc pure nothrow scope bool isEmptyXML() const { return false; } // Returns false always -} - -/* - * Class representing a text (aka Parsed Character Data) section - */ -class Text : Item -{ - private string content; - - /* - * Construct a text (aka PCData) section - * - * Params: - * content = the text. This function encodes the text before - * insertion, so it is safe to insert any text - * - * Example: - * -------------- - * auto Text = new CData("a < b"); - * // constructs a < b - * -------------- - */ - this(string content) @safe pure - { - this.content = encode(content); - } - - /* - * Compares two text sections for equality - * - * Example: - * -------------- - * Text item1,item2; - * if (item1 == item2) { } - * -------------- - */ - override bool opEquals(scope const Object o) const - { - const item = toType!(const Item)(o); - const t = cast(const Text) item; - return t !is null && content == t.content; - } - - /* - * Compares two text sections - * - * You should rarely need to call this function. It exists so that Texts - * can be used as associative array keys. - * - * Example: - * -------------- - * Text item1,item2; - * if (item1 < item2) { } - * -------------- - */ - override int opCmp(scope const Object o) scope const - { - const item = toType!(const Item)(o); - const t = cast(const Text) item; - return t !is null - && (content != t.content ? (content < t.content ? -1 : 1 ) : 0 ); - } - - /* - * Returns the hash of a text section - * - * You should rarely need to call this function. It exists so that Texts - * can be used as associative array keys. - */ - override size_t toHash() scope const nothrow { return hash(content); } - - /* - * Returns a string representation of this Text section - */ - override string toString() scope const @safe @nogc pure nothrow { return content; } - - /* - * Returns true if the content is the empty string - */ - override @property @safe @nogc pure nothrow scope bool isEmptyXML() const { return content.length == 0; } -} - -/* - * Class representing an XML Instruction section - */ -class XMLInstruction : Item -{ - private string content; - - /* - * Construct an XML Instruction section - * - * Params: - * content = the body of the instruction segment - * - * Throws: XIException if the segment body is illegal (contains ">") - * - * Example: - * -------------- - * auto item = new XMLInstruction("ATTLIST"); - * // constructs - * -------------- - */ - this(string content) @safe pure - { - import std.string : indexOf; - if (content.indexOf(">") != -1) throw new XIException(content); - this.content = content; - } - - /* - * Compares two XML instructions for equality - * - * Example: - * -------------- - * XMLInstruction item1,item2; - * if (item1 == item2) { } - * -------------- - */ - override bool opEquals(scope const Object o) const - { - const item = toType!(const Item)(o); - const t = cast(const XMLInstruction) item; - return t !is null && content == t.content; - } - - /* - * Compares two XML instructions - * - * You should rarely need to call this function. It exists so that - * XmlInstructions can be used as associative array keys. - * - * Example: - * -------------- - * XMLInstruction item1,item2; - * if (item1 < item2) { } - * -------------- - */ - override int opCmp(scope const Object o) scope const - { - const item = toType!(const Item)(o); - const t = cast(const XMLInstruction) item; - return t !is null - && (content != t.content ? (content < t.content ? -1 : 1 ) : 0 ); - } - - /* - * Returns the hash of an XMLInstruction - * - * You should rarely need to call this function. It exists so that - * XmlInstructions can be used as associative array keys. - */ - override size_t toHash() scope const nothrow { return hash(content); } - - /* - * Returns a string representation of this XmlInstruction - */ - override string toString() scope const @safe pure nothrow { return ""; } - - override @property @safe @nogc pure nothrow scope bool isEmptyXML() const { return false; } // Returns false always -} - -/* - * Class representing a Processing Instruction section - */ -class ProcessingInstruction : Item -{ - private string content; - - /* - * Construct a Processing Instruction section - * - * Params: - * content = the body of the instruction segment - * - * Throws: PIException if the segment body is illegal (contains "?>") - * - * Example: - * -------------- - * auto item = new ProcessingInstruction("php"); - * // constructs - * -------------- - */ - this(string content) @safe pure - { - import std.string : indexOf; - if (content.indexOf("?>") != -1) throw new PIException(content); - this.content = content; - } - - /* - * Compares two processing instructions for equality - * - * Example: - * -------------- - * ProcessingInstruction item1,item2; - * if (item1 == item2) { } - * -------------- - */ - override bool opEquals(scope const Object o) const - { - const item = toType!(const Item)(o); - const t = cast(const ProcessingInstruction) item; - return t !is null && content == t.content; - } - - /* - * Compares two processing instructions - * - * You should rarely need to call this function. It exists so that - * ProcessingInstructions can be used as associative array keys. - * - * Example: - * -------------- - * ProcessingInstruction item1,item2; - * if (item1 < item2) { } - * -------------- - */ - override int opCmp(scope const Object o) scope const - { - const item = toType!(const Item)(o); - const t = cast(const ProcessingInstruction) item; - return t !is null - && (content != t.content ? (content < t.content ? -1 : 1 ) : 0 ); - } - - /* - * Returns the hash of a ProcessingInstruction - * - * You should rarely need to call this function. It exists so that - * ProcessingInstructions can be used as associative array keys. - */ - override size_t toHash() scope const nothrow { return hash(content); } - - /* - * Returns a string representation of this ProcessingInstruction - */ - override string toString() scope const @safe pure nothrow { return ""; } - - override @property @safe @nogc pure nothrow bool isEmptyXML() scope const { return false; } // Returns false always -} - -/* - * Abstract base class for XML items - */ -abstract class Item -{ - // Compares with another Item of same type for equality - abstract override bool opEquals(scope const Object o) @safe const; - - // Compares with another Item of same type - abstract override int opCmp(scope const Object o) @safe const; - - // Returns the hash of this item - abstract override size_t toHash() @safe scope const; - - // Returns a string representation of this item - abstract override string toString() @safe scope const; - - /* - * Returns an indented string representation of this item - * - * Params: - * indent = number of spaces by which to indent child elements - */ - string[] pretty(uint indent) @safe scope const - { - import std.string : strip; - string s = strip(toString()); - return s.length == 0 ? [] : [ s ]; - } - - // Returns true if the item represents empty XML text - abstract @property @safe @nogc pure nothrow bool isEmptyXML() scope const; -} - -/* - * Class for parsing an XML Document. - * - * This is a subclass of ElementParser. Most of the useful functions are - * documented there. - * - * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) - * - * Bugs: - * Currently only supports UTF documents. - * - * If there is an encoding attribute in the prolog, it is ignored. - * - */ -class DocumentParser : ElementParser -{ - string xmlText; - - /* - * Constructs a DocumentParser. - * - * The input to this function MUST be valid XML. - * This is enforced by the function's in contract. - * - * Params: - * xmlText_ = the entire XML document as text - * - */ - this(string xmlText_) - in - { - assert(xmlText_.length != 0); - try - { - // Confirm that the input is valid XML - check(xmlText_); - } - catch (CheckException e) - { - // And if it's not, tell the user why not - assert(false, "\n" ~ e.toString()); - } - } - do - { - xmlText = xmlText_; - s = &xmlText; - super(); // Initialize everything - parse(); // Parse through the root tag (but not beyond) - } -} - -@system unittest -{ - auto doc = new Document(""); - assert(doc.elements.length == 1); - assert(doc.elements[0].tag.name == "child"); - assert(doc.items == doc.elements); -} - -/* - * Class for parsing an XML element. - * - * Standards: $(LINK2 http://www.w3.org/TR/1998/REC-xml-19980210, XML 1.0) - * - * Note that you cannot construct instances of this class directly. You can - * construct a DocumentParser (which is a subclass of ElementParser), but - * otherwise, Instances of ElementParser will be created for you by the - * library, and passed your way via onStartTag handlers. - * - */ -class ElementParser -{ - alias Handler = void delegate(string); - alias ElementHandler = void delegate(in Element element); - alias ParserHandler = void delegate(ElementParser parser); - - private - { - Tag tag_; - string elementStart; - string* s; - - Handler commentHandler = null; - Handler cdataHandler = null; - Handler xiHandler = null; - Handler piHandler = null; - Handler rawTextHandler = null; - Handler textHandler = null; - - // Private constructor for start tags - this(ElementParser parent) @safe @nogc pure nothrow - { - s = parent.s; - this(); - tag_ = parent.tag_; - } - - // Private constructor for empty tags - this(Tag tag, string* t) @safe @nogc pure nothrow - { - s = t; - this(); - tag_ = tag; - } - } - - /* - * The Tag at the start of the element being parsed. You can read this to - * determine the tag's name and attributes. - */ - @property @safe @nogc pure nothrow const(Tag) tag() const { return tag_; } - - /* - * Register a handler which will be called whenever a start tag is - * encountered which matches the specified name. You can also pass null as - * the name, in which case the handler will be called for any unmatched - * start tag. - * - * Example: - * -------------- - * // Call this function whenever a start tag is encountered - * onStartTag["podcast"] = (ElementParser xml) - * { - * // Your code here - * // - * // This is a a closure, so code here may reference - * // variables which are outside of this scope - * }; - * - * // call myEpisodeStartHandler (defined elsewhere) whenever an - * // start tag is encountered - * onStartTag["episode"] = &myEpisodeStartHandler; - * - * // call delegate dg for all other start tags - * onStartTag[null] = dg; - * -------------- - * - * This library will supply your function with a new instance of - * ElementHandler, which may be used to parse inside the element whose - * start tag was just found, or to identify the tag attributes of the - * element, etc. - * - * Note that your function will be called for both start tags and empty - * tags. That is, we make no distinction between <br></br> - * and <br/>. - */ - ParserHandler[string] onStartTag; - - /* - * Register a handler which will be called whenever an end tag is - * encountered which matches the specified name. You can also pass null as - * the name, in which case the handler will be called for any unmatched - * end tag. - * - * Example: - * -------------- - * // Call this function whenever a end tag is encountered - * onEndTag["podcast"] = (in Element e) - * { - * // Your code here - * // - * // This is a a closure, so code here may reference - * // variables which are outside of this scope - * }; - * - * // call myEpisodeEndHandler (defined elsewhere) whenever an - * // end tag is encountered - * onEndTag["episode"] = &myEpisodeEndHandler; - * - * // call delegate dg for all other end tags - * onEndTag[null] = dg; - * -------------- - * - * Note that your function will be called for both start tags and empty - * tags. That is, we make no distinction between <br></br> - * and <br/>. - */ - ElementHandler[string] onEndTag; - - protected this() @safe @nogc pure nothrow - { - elementStart = *s; - } - - /* - * Register a handler which will be called whenever text is encountered. - * - * Example: - * -------------- - * // Call this function whenever text is encountered - * onText = (string s) - * { - * // Your code here - * - * // The passed parameter s will have been decoded by the time you see - * // it, and so may contain any character. - * // - * // This is a a closure, so code here may reference - * // variables which are outside of this scope - * }; - * -------------- - */ - @property @safe @nogc pure nothrow void onText(Handler handler) { textHandler = handler; } - - /* - * Register an alternative handler which will be called whenever text - * is encountered. This differs from onText in that onText will decode - * the text, whereas onTextRaw will not. This allows you to make design - * choices, since onText will be more accurate, but slower, while - * onTextRaw will be faster, but less accurate. Of course, you can - * still call decode() within your handler, if you want, but you'd - * probably want to use onTextRaw only in circumstances where you - * know that decoding is unnecessary. - * - * Example: - * -------------- - * // Call this function whenever text is encountered - * onText = (string s) - * { - * // Your code here - * - * // The passed parameter s will NOT have been decoded. - * // - * // This is a a closure, so code here may reference - * // variables which are outside of this scope - * }; - * -------------- - */ - @safe @nogc pure nothrow void onTextRaw(Handler handler) { rawTextHandler = handler; } - - /* - * Register a handler which will be called whenever a character data - * segment is encountered. - * - * Example: - * -------------- - * // Call this function whenever a CData section is encountered - * onCData = (string s) - * { - * // Your code here - * - * // The passed parameter s does not include the opening - * // - * // This is a a closure, so code here may reference - * // variables which are outside of this scope - * }; - * -------------- - */ - @property @safe @nogc pure nothrow void onCData(Handler handler) { cdataHandler = handler; } - - /* - * Register a handler which will be called whenever a comment is - * encountered. - * - * Example: - * -------------- - * // Call this function whenever a comment is encountered - * onComment = (string s) - * { - * // Your code here - * - * // The passed parameter s does not include the opening - * // - * // This is a a closure, so code here may reference - * // variables which are outside of this scope - * }; - * -------------- - */ - @property @safe @nogc pure nothrow void onComment(Handler handler) { commentHandler = handler; } - - /* - * Register a handler which will be called whenever a processing - * instruction is encountered. - * - * Example: - * -------------- - * // Call this function whenever a processing instruction is encountered - * onPI = (string s) - * { - * // Your code here - * - * // The passed parameter s does not include the opening - * // - * // This is a a closure, so code here may reference - * // variables which are outside of this scope - * }; - * -------------- - */ - @property @safe @nogc pure nothrow void onPI(Handler handler) { piHandler = handler; } - - /* - * Register a handler which will be called whenever an XML instruction is - * encountered. - * - * Example: - * -------------- - * // Call this function whenever an XML instruction is encountered - * // (Note: XML instructions may only occur preceding the root tag of a - * // document). - * onPI = (string s) - * { - * // Your code here - * - * // The passed parameter s does not include the opening - * // - * // This is a a closure, so code here may reference - * // variables which are outside of this scope - * }; - * -------------- - */ - @property @safe @nogc pure nothrow void onXI(Handler handler) { xiHandler = handler; } - - /* - * Parse an XML element. - * - * Parsing will continue until the end of the current element. Any items - * encountered for which a handler has been registered will invoke that - * handler. - * - * Throws: various kinds of XMLException - */ - void parse() - { - import std.algorithm.searching : startsWith; - import std.string : indexOf; - - string t; - const Tag root = tag_; - Tag[string] startTags; - if (tag_ !is null) startTags[tag_.name] = tag_; - - while (s.length != 0) - { - if (startsWith(*s,"")); - if (commentHandler.funcptr !is null) commentHandler(t); - chop(*s,3); - } - else if (startsWith(*s,"")); - if (cdataHandler.funcptr !is null) cdataHandler(t); - chop(*s,3); - } - else if (startsWith(*s,"")); - if (xiHandler.funcptr !is null) xiHandler(t); - chop(*s,1); - } - else if (startsWith(*s,"")); - if (piHandler.funcptr !is null) piHandler(t); - chop(*s,2); - } - else if (startsWith(*s,"<")) - { - tag_ = new Tag(*s,true); - if (root is null) - return; // Return to constructor of derived class - - if (tag_.isStart) - { - startTags[tag_.name] = tag_; - - auto parser = new ElementParser(this); - - auto handler = tag_.name in onStartTag; - if (handler !is null) (*handler)(parser); - else - { - handler = null in onStartTag; - if (handler !is null) (*handler)(parser); - } - } - else if (tag_.isEnd) - { - const startTag = startTags[tag_.name]; - string text; - - if (startTag.tagString.length == 0) - assert(0); - - immutable(char)* p = startTag.tagString.ptr - + startTag.tagString.length; - immutable(char)* q = &tag_.tagString[0]; - text = decode(p[0..(q-p)], DecodeMode.LOOSE); - - auto element = new Element(startTag); - if (text.length != 0) element ~= new Text(text); - - auto handler = tag_.name in onEndTag; - if (handler !is null) (*handler)(element); - else - { - handler = null in onEndTag; - if (handler !is null) (*handler)(element); - } - - if (tag_.name == root.name) return; - } - else if (tag_.isEmpty) - { - Tag startTag = new Tag(tag_.name); - - // FIX by hed010gy - // https://issues.dlang.org/show_bug.cgi?id=2979 - if (tag_.attr.length > 0) - foreach (tn,tv; tag_.attr) startTag.attr[tn]=tv; - // END FIX - - // Handle the pretend start tag - string s2; - auto parser = new ElementParser(startTag,&s2); - auto handler1 = startTag.name in onStartTag; - if (handler1 !is null) (*handler1)(parser); - else - { - handler1 = null in onStartTag; - if (handler1 !is null) (*handler1)(parser); - } - - // Handle the pretend end tag - auto element = new Element(startTag); - auto handler2 = tag_.name in onEndTag; - if (handler2 !is null) (*handler2)(element); - else - { - handler2 = null in onEndTag; - if (handler2 !is null) (*handler2)(element); - } - } - } - else - { - t = chop(*s,indexOf(*s,"<")); - if (rawTextHandler.funcptr !is null) - rawTextHandler(t); - else if (textHandler.funcptr !is null) - textHandler(decode(t,DecodeMode.LOOSE)); - } - } - } - - /* - * Returns that part of the element which has already been parsed - */ - override string toString() const @nogc @safe pure nothrow - { - assert(elementStart.length >= s.length); - return elementStart[0 .. elementStart.length - s.length]; - } - -} - -private -{ - template Check(string msg) - { - string old = s; - - void fail() @safe pure - { - s = old; - throw new Err(s,msg); - } - - void fail(Err e) @safe pure - { - s = old; - throw new Err(s,msg,e); - } - - void fail(string msg2) @safe pure - { - fail(new Err(s,msg2)); - } - } - - void checkMisc(ref string s) @safe pure // rule 27 - { - import std.algorithm.searching : startsWith; - - mixin Check!("Misc"); - - try - { - if (s.startsWith("",s); } catch (Err e) { fail(e); } - } - - void checkPI(ref string s) @safe pure // rule 16 - { - mixin Check!("PI"); - - try - { - checkLiteral("",s); - } - catch (Err e) { fail(e); } - } - - void checkCDSect(ref string s) @safe pure // rule 18 - { - mixin Check!("CDSect"); - - try - { - checkLiteral(cdata,s); - checkEnd("]]>",s); - } - catch (Err e) { fail(e); } - } - - void checkProlog(ref string s) @safe pure // rule 22 - { - mixin Check!("Prolog"); - - try - { - /* The XML declaration is optional - * http://www.w3.org/TR/2008/REC-xml-20081126/#NT-prolog - */ - opt!(checkXMLDecl)(s); - - star!(checkMisc)(s); - opt!(seq!(checkDocTypeDecl,star!(checkMisc)))(s); - } - catch (Err e) { fail(e); } - } - - void checkXMLDecl(ref string s) @safe pure // rule 23 - { - mixin Check!("XMLDecl"); - - try - { - checkLiteral("",s); - } - catch (Err e) { fail(e); } - } - - void checkVersionInfo(ref string s) @safe pure // rule 24 - { - mixin Check!("VersionInfo"); - - try - { - checkSpace(s); - checkLiteral("version",s); - checkEq(s); - quoted!(checkVersionNum)(s); - } - catch (Err e) { fail(e); } - } - - void checkEq(ref string s) @safe pure // rule 25 - { - mixin Check!("Eq"); - - try - { - opt!(checkSpace)(s); - checkLiteral("=",s); - opt!(checkSpace)(s); - } - catch (Err e) { fail(e); } - } - - void checkVersionNum(ref string s) @safe pure // rule 26 - { - import std.algorithm.searching : countUntil; - import std.utf : byCodeUnit; - - mixin Check!("VersionNum"); - - s = s[s.byCodeUnit.countUntil('\"') .. $]; - if (s is old) fail(); - } - - void checkDocTypeDecl(ref string s) @safe pure // rule 28 - { - mixin Check!("DocTypeDecl"); - - try - { - checkLiteral("",s); - } - catch (Err e) { fail(e); } - } - - void checkSDDecl(ref string s) @safe pure // rule 32 - { - import std.algorithm.searching : startsWith; - - mixin Check!("SDDecl"); - - try - { - checkSpace(s); - checkLiteral("standalone",s); - checkEq(s); - } - catch (Err e) { fail(e); } - - int n = 0; - if (s.startsWith("'yes'") || s.startsWith("\"yes\"")) n = 5; - else if (s.startsWith("'no'" ) || s.startsWith("\"no\"" )) n = 4; - else fail("standalone attribute value must be 'yes', \"yes\","~ - " 'no' or \"no\""); - s = s[n..$]; - } - - void checkElement(ref string s) @safe pure // rule 39 - { - mixin Check!("Element"); - - string sname,ename,t; - try { checkTag(s,t,sname); } catch (Err e) { fail(e); } - - if (t == "STag") - { - try - { - checkContent(s); - t = s; - checkETag(s,ename); - } - catch (Err e) { fail(e); } - - if (sname != ename) - { - s = t; - fail("end tag name \"" ~ ename - ~ "\" differs from start tag name \""~sname~"\""); - } - } - } - - // rules 40 and 44 - void checkTag(ref string s, out string type, out string name) @safe pure - { - mixin Check!("Tag"); - - try - { - type = "STag"; - checkLiteral("<",s); - checkName(s,name); - star!(seq!(checkSpace,checkAttribute))(s); - opt!(checkSpace)(s); - if (s.length != 0 && s[0] == '/') - { - s = s[1..$]; - type = "ETag"; - } - checkLiteral(">",s); - } - catch (Err e) { fail(e); } - } - - void checkAttribute(ref string s) @safe pure // rule 41 - { - mixin Check!("Attribute"); - - try - { - string name; - checkName(s,name); - checkEq(s); - checkAttValue(s); - } - catch (Err e) { fail(e); } - } - - void checkETag(ref string s, out string name) @safe pure // rule 42 - { - mixin Check!("ETag"); - - try - { - checkLiteral("",s); - } - catch (Err e) { fail(e); } - } - - void checkContent(ref string s) @safe pure // rule 43 - { - import std.algorithm.searching : startsWith; - - mixin Check!("Content"); - - try - { - while (s.length != 0) - { - old = s; - if (s.startsWith("&")) { checkReference(s); } - else if (s.startsWith(" - B - -EOS"; - try - { - check(s); - } - catch (CheckException e) - { - assert(0, e.toString()); - } -} - -@system unittest -{ - string test_xml = ` - `; - - DocumentParser parser = new DocumentParser(test_xml); - bool tested = false; - parser.onStartTag["stream:stream"] = (ElementParser p) { - assert(p.tag.attr["xmlns"] == "jabber:'client'"); - assert(p.tag.attr["from"] == "jid.pl"); - assert(p.tag.attr["attr"] == "a\"b\"c"); - tested = true; - }; - parser.parse(); - assert(tested); -} - -@system unittest -{ - string s = q"EOS - - What & Up Second - -EOS"; - auto xml = new DocumentParser(s); - - xml.onStartTag["Test"] = (ElementParser xml) { - assert(xml.tag.attr["thing"] == "What & Up"); - }; - - xml.onEndTag["Test"] = (in Element e) { - assert(e.text() == "What & Up Second"); - }; - xml.parse(); -} - -@system unittest -{ - string s = ``; - auto doc = new Document(s); - assert(doc.toString() == s); -} - -/* The base class for exceptions thrown by this module */ -class XMLException : Exception { this(string msg) @safe pure { super(msg); } } - -// Other exceptions - -// Thrown during Comment constructor -class CommentException : XMLException -{ private this(string msg) @safe pure { super(msg); } } - -// Thrown during CData constructor -class CDataException : XMLException -{ private this(string msg) @safe pure { super(msg); } } - -// Thrown during XMLInstruction constructor -class XIException : XMLException -{ private this(string msg) @safe pure { super(msg); } } - -// Thrown during ProcessingInstruction constructor -class PIException : XMLException -{ private this(string msg) @safe pure { super(msg); } } - -// Thrown during Text constructor -class TextException : XMLException -{ private this(string msg) @safe pure { super(msg); } } - -// Thrown during decode() -class DecodeException : XMLException -{ private this(string msg) @safe pure { super(msg); } } - -// Thrown if comparing with wrong type -class InvalidTypeException : XMLException -{ private this(string msg) @safe pure { super(msg); } } - -// Thrown when parsing for Tags -class TagException : XMLException -{ private this(string msg) @safe pure { super(msg); } } - -/* - * Thrown during check() - */ -class CheckException : XMLException -{ - CheckException err; // Parent in hierarchy - private string tail; - /* - * Name of production rule which failed to parse, - * or specific error message - */ - string msg; - size_t line = 0; // Line number at which parse failure occurred - size_t column = 0; // Column number at which parse failure occurred - - private this(string tail,string msg,Err err=null) @safe pure - { - super(null); - this.tail = tail; - this.msg = msg; - this.err = err; - } - - private void complete(string entire) @safe pure - { - import std.string : count, lastIndexOf; - import std.utf : toUTF32; - - string head = entire[0..$-tail.length]; - ptrdiff_t n = head.lastIndexOf('\n') + 1; - line = head.count("\n") + 1; - dstring t = toUTF32(head[n..$]); - column = t.length + 1; - if (err !is null) err.complete(entire); - } - - override string toString() const @safe pure - { - import std.format : format; - - string s; - if (line != 0) s = format("Line %d, column %d: ",line,column); - s ~= msg; - s ~= '\n'; - if (err !is null) s = err.toString() ~ s; - return s; - } -} - -private alias Err = CheckException; - -// Private helper functions - -private -{ - inout(T) toType(T)(inout return scope Object o) - { - T t = cast(T)(o); - if (t is null) - { - throw new InvalidTypeException("Attempt to compare a " - ~ T.stringof ~ " with an instance of another type"); - } - return t; - } - - string chop(ref string s, size_t n) @safe pure nothrow - { - if (n == -1) n = s.length; - string t = s[0 .. n]; - s = s[n..$]; - return t; - } - - bool optc(ref string s, char c) @safe pure nothrow - { - immutable bool b = s.length != 0 && s[0] == c; - if (b) s = s[1..$]; - return b; - } - - void reqc(ref string s, char c) @safe pure - { - if (s.length == 0 || s[0] != c) throw new TagException(""); - s = s[1..$]; - } - - char requireOneOf(ref string s, string chars) @safe pure - { - import std.string : indexOf; - - if (s.length == 0 || indexOf(chars,s[0]) == -1) - throw new TagException(""); - immutable char ch = s[0]; - s = s[1..$]; - return ch; - } - - alias hash = .hashOf; - - // Definitions from the XML specification - immutable CharTable=[0x9,0x9,0xA,0xA,0xD,0xD,0x20,0xD7FF,0xE000,0xFFFD, - 0x10000,0x10FFFF]; - immutable BaseCharTable=[0x0041,0x005A,0x0061,0x007A,0x00C0,0x00D6,0x00D8, - 0x00F6,0x00F8,0x00FF,0x0100,0x0131,0x0134,0x013E,0x0141,0x0148,0x014A, - 0x017E,0x0180,0x01C3,0x01CD,0x01F0,0x01F4,0x01F5,0x01FA,0x0217,0x0250, - 0x02A8,0x02BB,0x02C1,0x0386,0x0386,0x0388,0x038A,0x038C,0x038C,0x038E, - 0x03A1,0x03A3,0x03CE,0x03D0,0x03D6,0x03DA,0x03DA,0x03DC,0x03DC,0x03DE, - 0x03DE,0x03E0,0x03E0,0x03E2,0x03F3,0x0401,0x040C,0x040E,0x044F,0x0451, - 0x045C,0x045E,0x0481,0x0490,0x04C4,0x04C7,0x04C8,0x04CB,0x04CC,0x04D0, - 0x04EB,0x04EE,0x04F5,0x04F8,0x04F9,0x0531,0x0556,0x0559,0x0559,0x0561, - 0x0586,0x05D0,0x05EA,0x05F0,0x05F2,0x0621,0x063A,0x0641,0x064A,0x0671, - 0x06B7,0x06BA,0x06BE,0x06C0,0x06CE,0x06D0,0x06D3,0x06D5,0x06D5,0x06E5, - 0x06E6,0x0905,0x0939,0x093D,0x093D,0x0958,0x0961,0x0985,0x098C,0x098F, - 0x0990,0x0993,0x09A8,0x09AA,0x09B0,0x09B2,0x09B2,0x09B6,0x09B9,0x09DC, - 0x09DD,0x09DF,0x09E1,0x09F0,0x09F1,0x0A05,0x0A0A,0x0A0F,0x0A10,0x0A13, - 0x0A28,0x0A2A,0x0A30,0x0A32,0x0A33,0x0A35,0x0A36,0x0A38,0x0A39,0x0A59, - 0x0A5C,0x0A5E,0x0A5E,0x0A72,0x0A74,0x0A85,0x0A8B,0x0A8D,0x0A8D,0x0A8F, - 0x0A91,0x0A93,0x0AA8,0x0AAA,0x0AB0,0x0AB2,0x0AB3,0x0AB5,0x0AB9,0x0ABD, - 0x0ABD,0x0AE0,0x0AE0,0x0B05,0x0B0C,0x0B0F,0x0B10,0x0B13,0x0B28,0x0B2A, - 0x0B30,0x0B32,0x0B33,0x0B36,0x0B39,0x0B3D,0x0B3D,0x0B5C,0x0B5D,0x0B5F, - 0x0B61,0x0B85,0x0B8A,0x0B8E,0x0B90,0x0B92,0x0B95,0x0B99,0x0B9A,0x0B9C, - 0x0B9C,0x0B9E,0x0B9F,0x0BA3,0x0BA4,0x0BA8,0x0BAA,0x0BAE,0x0BB5,0x0BB7, - 0x0BB9,0x0C05,0x0C0C,0x0C0E,0x0C10,0x0C12,0x0C28,0x0C2A,0x0C33,0x0C35, - 0x0C39,0x0C60,0x0C61,0x0C85,0x0C8C,0x0C8E,0x0C90,0x0C92,0x0CA8,0x0CAA, - 0x0CB3,0x0CB5,0x0CB9,0x0CDE,0x0CDE,0x0CE0,0x0CE1,0x0D05,0x0D0C,0x0D0E, - 0x0D10,0x0D12,0x0D28,0x0D2A,0x0D39,0x0D60,0x0D61,0x0E01,0x0E2E,0x0E30, - 0x0E30,0x0E32,0x0E33,0x0E40,0x0E45,0x0E81,0x0E82,0x0E84,0x0E84,0x0E87, - 0x0E88,0x0E8A,0x0E8A,0x0E8D,0x0E8D,0x0E94,0x0E97,0x0E99,0x0E9F,0x0EA1, - 0x0EA3,0x0EA5,0x0EA5,0x0EA7,0x0EA7,0x0EAA,0x0EAB,0x0EAD,0x0EAE,0x0EB0, - 0x0EB0,0x0EB2,0x0EB3,0x0EBD,0x0EBD,0x0EC0,0x0EC4,0x0F40,0x0F47,0x0F49, - 0x0F69,0x10A0,0x10C5,0x10D0,0x10F6,0x1100,0x1100,0x1102,0x1103,0x1105, - 0x1107,0x1109,0x1109,0x110B,0x110C,0x110E,0x1112,0x113C,0x113C,0x113E, - 0x113E,0x1140,0x1140,0x114C,0x114C,0x114E,0x114E,0x1150,0x1150,0x1154, - 0x1155,0x1159,0x1159,0x115F,0x1161,0x1163,0x1163,0x1165,0x1165,0x1167, - 0x1167,0x1169,0x1169,0x116D,0x116E,0x1172,0x1173,0x1175,0x1175,0x119E, - 0x119E,0x11A8,0x11A8,0x11AB,0x11AB,0x11AE,0x11AF,0x11B7,0x11B8,0x11BA, - 0x11BA,0x11BC,0x11C2,0x11EB,0x11EB,0x11F0,0x11F0,0x11F9,0x11F9,0x1E00, - 0x1E9B,0x1EA0,0x1EF9,0x1F00,0x1F15,0x1F18,0x1F1D,0x1F20,0x1F45,0x1F48, - 0x1F4D,0x1F50,0x1F57,0x1F59,0x1F59,0x1F5B,0x1F5B,0x1F5D,0x1F5D,0x1F5F, - 0x1F7D,0x1F80,0x1FB4,0x1FB6,0x1FBC,0x1FBE,0x1FBE,0x1FC2,0x1FC4,0x1FC6, - 0x1FCC,0x1FD0,0x1FD3,0x1FD6,0x1FDB,0x1FE0,0x1FEC,0x1FF2,0x1FF4,0x1FF6, - 0x1FFC,0x2126,0x2126,0x212A,0x212B,0x212E,0x212E,0x2180,0x2182,0x3041, - 0x3094,0x30A1,0x30FA,0x3105,0x312C,0xAC00,0xD7A3]; - immutable IdeographicTable=[0x3007,0x3007,0x3021,0x3029,0x4E00,0x9FA5]; - immutable CombiningCharTable=[0x0300,0x0345,0x0360,0x0361,0x0483,0x0486, - 0x0591,0x05A1,0x05A3,0x05B9,0x05BB,0x05BD,0x05BF,0x05BF,0x05C1,0x05C2, - 0x05C4,0x05C4,0x064B,0x0652,0x0670,0x0670,0x06D6,0x06DC,0x06DD,0x06DF, - 0x06E0,0x06E4,0x06E7,0x06E8,0x06EA,0x06ED,0x0901,0x0903,0x093C,0x093C, - 0x093E,0x094C,0x094D,0x094D,0x0951,0x0954,0x0962,0x0963,0x0981,0x0983, - 0x09BC,0x09BC,0x09BE,0x09BE,0x09BF,0x09BF,0x09C0,0x09C4,0x09C7,0x09C8, - 0x09CB,0x09CD,0x09D7,0x09D7,0x09E2,0x09E3,0x0A02,0x0A02,0x0A3C,0x0A3C, - 0x0A3E,0x0A3E,0x0A3F,0x0A3F,0x0A40,0x0A42,0x0A47,0x0A48,0x0A4B,0x0A4D, - 0x0A70,0x0A71,0x0A81,0x0A83,0x0ABC,0x0ABC,0x0ABE,0x0AC5,0x0AC7,0x0AC9, - 0x0ACB,0x0ACD,0x0B01,0x0B03,0x0B3C,0x0B3C,0x0B3E,0x0B43,0x0B47,0x0B48, - 0x0B4B,0x0B4D,0x0B56,0x0B57,0x0B82,0x0B83,0x0BBE,0x0BC2,0x0BC6,0x0BC8, - 0x0BCA,0x0BCD,0x0BD7,0x0BD7,0x0C01,0x0C03,0x0C3E,0x0C44,0x0C46,0x0C48, - 0x0C4A,0x0C4D,0x0C55,0x0C56,0x0C82,0x0C83,0x0CBE,0x0CC4,0x0CC6,0x0CC8, - 0x0CCA,0x0CCD,0x0CD5,0x0CD6,0x0D02,0x0D03,0x0D3E,0x0D43,0x0D46,0x0D48, - 0x0D4A,0x0D4D,0x0D57,0x0D57,0x0E31,0x0E31,0x0E34,0x0E3A,0x0E47,0x0E4E, - 0x0EB1,0x0EB1,0x0EB4,0x0EB9,0x0EBB,0x0EBC,0x0EC8,0x0ECD,0x0F18,0x0F19, - 0x0F35,0x0F35,0x0F37,0x0F37,0x0F39,0x0F39,0x0F3E,0x0F3E,0x0F3F,0x0F3F, - 0x0F71,0x0F84,0x0F86,0x0F8B,0x0F90,0x0F95,0x0F97,0x0F97,0x0F99,0x0FAD, - 0x0FB1,0x0FB7,0x0FB9,0x0FB9,0x20D0,0x20DC,0x20E1,0x20E1,0x302A,0x302F, - 0x3099,0x3099,0x309A,0x309A]; - immutable DigitTable=[0x0030,0x0039,0x0660,0x0669,0x06F0,0x06F9,0x0966, - 0x096F,0x09E6,0x09EF,0x0A66,0x0A6F,0x0AE6,0x0AEF,0x0B66,0x0B6F,0x0BE7, - 0x0BEF,0x0C66,0x0C6F,0x0CE6,0x0CEF,0x0D66,0x0D6F,0x0E50,0x0E59,0x0ED0, - 0x0ED9,0x0F20,0x0F29]; - immutable ExtenderTable=[0x00B7,0x00B7,0x02D0,0x02D0,0x02D1,0x02D1,0x0387, - 0x0387,0x0640,0x0640,0x0E46,0x0E46,0x0EC6,0x0EC6,0x3005,0x3005,0x3031, - 0x3035,0x309D,0x309E,0x30FC,0x30FE]; - - bool lookup(const(int)[] table, int c) @safe @nogc nothrow pure - { - while (table.length != 0) - { - auto m = (table.length >> 1) & ~1; - if (c < table[m]) - { - table = table[0 .. m]; - } - else if (c > table[m+1]) - { - table = table[m+2..$]; - } - else return true; - } - return false; - } - - string startOf(string s) @safe nothrow pure - { - string r; - foreach (char c;s) - { - r ~= (c < 0x20 || c > 0x7F) ? '.' : c; - if (r.length >= 40) { r ~= "___"; break; } - } - return r; - } - - void exit(string s=null) - { - throw new XMLException(s); - } -} diff --git a/win32.mak b/win32.mak index a0edbdf0aeb..9f86baa6149 100644 --- a/win32.mak +++ b/win32.mak @@ -132,7 +132,6 @@ SRC_STD_3a= \ std\typetuple.d \ std\traits.d \ std\encoding.d \ - std\xml.d \ std\random.d \ std\exception.d \ std\compiler.d \ @@ -203,7 +202,6 @@ SRC_STD_DIGEST= \ std\digest\sha.d \ std\digest\md.d \ std\digest\ripemd.d \ - std\digest\digest.d \ std\digest\hmac.d \ std\digest\murmurhash.d \ std\digest\package.d @@ -280,7 +278,7 @@ SRC_STD_INTERNAL_WINDOWS= \ std\internal\windows\advapi32.d SRC_STD_EXP= \ - std\checkedint.d std\experimental\checkedint.d std\experimental\typecons.d + std\checkedint.d std\experimental\checkedint.d SRC_STD_UNI = std\uni\package.d \ @@ -485,7 +483,6 @@ cov : $(SRC_TO_COMPILE) $(LIB) $(DMD) -conf= -cov=ctfe -cov=100 $(UDFLAGS) -main -run std\typetuple.d $(DMD) -conf= -cov=ctfe -cov=85 $(UDFLAGS) -main -run std\traits.d $(DMD) -conf= -cov=ctfe -cov=62 $(UDFLAGS) -main -run std\encoding.d - $(DMD) -conf= -cov=ctfe -cov=61 $(UDFLAGS) -main -run std\xml.d $(DMD) -conf= -cov=ctfe -cov=79 $(UDFLAGS) -main -run std\random.d $(DMD) -conf= -cov=ctfe -cov=92 $(UDFLAGS) -main -run std\exception.d $(DMD) -conf= -cov=ctfe -cov=73 $(UDFLAGS) -main -run std\concurrency.d @@ -500,7 +497,6 @@ cov : $(SRC_TO_COMPILE) $(LIB) $(DMD) -conf= -cov=ctfe -cov=55 $(UDFLAGS) -main -run std\digest\sha.d $(DMD) -conf= -cov=ctfe -cov=100 $(UDFLAGS) -main -run std\digest\md.d $(DMD) -conf= -cov=ctfe -cov=100 $(UDFLAGS) -main -run std\digest\ripemd.d - $(DMD) -conf= -cov=ctfe -cov=75 $(UDFLAGS) -main -run std\digest\digest.d $(DMD) -conf= -cov=ctfe -cov=100 $(UDFLAGS) -main -run std\digest\hmac.d $(DMD) -conf= -cov=ctfe -cov=95 $(UDFLAGS) -main -run std\algorithm\package.d $(DMD) -conf= -cov=ctfe -cov=95 $(UDFLAGS) -main -run std\algorithm\comparison.d diff --git a/win64.mak b/win64.mak index 3bcc21ce019..39bf96940e2 100644 --- a/win64.mak +++ b/win64.mak @@ -121,7 +121,6 @@ SRC_STD_3c= \ std\typetuple.d \ std\traits.d \ std\encoding.d \ - std\xml.d \ std\random.d \ std\exception.d \ std\compiler.d \ @@ -232,7 +231,6 @@ SRC_STD_DIGEST= \ std\digest\sha.d \ std\digest\md.d \ std\digest\ripemd.d \ - std\digest\digest.d \ std\digest\hmac.d \ std\digest\murmurhash.d \ std\digest\package.d @@ -288,7 +286,7 @@ SRC_STD_INTERNAL_WINDOWS= \ std\internal\windows\advapi32.d SRC_STD_EXP= \ - std\checkedint.d std\experimental\checkedint.d std\experimental\typecons.d + std\checkedint.d std\experimental\checkedint.d SRC_STD_UNI = std\uni\package.d